【问题标题】:Reasonably portable way to get top 64-bits from 64x64 bit multiply? [duplicate]从 64x64 位乘法中获得前 64 位的合理可移植方式? [复制]
【发布时间】:2014-11-10 20:29:54
【问题描述】:

在 C/C++ 中是否有一种合理的可移植方式来将两个 64 位整数相乘以获得 128 位结果并获得结果的 top 64 位,而不是底部 64-位?我需要它来在任意大小的表上分配散列函数。

【问题讨论】:

  • 是的,有。如果你在纸上做(比方说)2 位小数乘法,你可以看到它是如何工作的。然后用两个 16 位操作数编写代码,这样您就可以轻松地检查各个阶段。但要小心你的签名/未签名变量!
  • 也许that 对你有帮助?
  • 先计算产品符号。使用未签名的。之后放回标志。
  • 如果语言不支持它(一些高级语言一直支持大于原生 int 的 int),那么在汇编程序中执行。乘积的高64位进入rdx,低64位进入rax
  • 如果这是一个散列函数,前 64 位是乘法的正确值还是只是一致是否重要?如果您担心速度,您可以快速为前 64 位计算一个不正确但一致的值。否则,如果您想要正确的值,则需要担心进位。我的意思是你可以按照李丹尼尔克罗克在他的回答中所说的去做,即使他的回答可能相差 1。

标签: c++ c


【解决方案1】:

此答案显示了如何在不支持 128 位整数的系统上从 64x64 位乘法中获得(确切的)前 64 位。 @amdn 的回答将在确实支持 128 位整数的系统上提供更好的性能。

下图显示了一种从两个 64 位数字计算 128 位乘积的方法。每个黑色矩形代表一个 64 位数字。该方法的 64 位输入 XY 被划分为标记为 abcd 的 32 位块。然后执行四次 32x32 位乘法,得到标记为 a*cb*ca*db*d 的四个 64 位乘积。四个乘积必须移位并相加才能计算出最终答案。

请注意,128 位乘积的低 32 位完全由部分乘积 b*d 的低 32 位决定。接下来的 32 位由下面的低 32 位决定

mid34 = ((b*c) & 0xffffffff) + ((a*d) & 0xffffffff) + ((b*d) >> 32);

请注意,mid34 是三个 32 位数字的和,因此实际上是一个 34 位的和。 mid34 的高两位作为进位到 64x64 位乘法的高 64 位。

这将我们带到演示代码。 top64 函数计算 64x64 乘法的高 64 位。允许在注释中显示低 64 位的计算有点冗长。 main 函数利用 128 位整数通过一个简单的测试用例来验证结果。进一步的测试留给读者作为练习。

#include <stdio.h>
#include <stdint.h>

typedef unsigned __int128 uint128_t;

uint64_t top64( uint64_t x, uint64_t y )
{
    uint64_t a = x >> 32, b = x & 0xffffffff;
    uint64_t c = y >> 32, d = y & 0xffffffff;

    uint64_t ac = a * c;
    uint64_t bc = b * c;
    uint64_t ad = a * d;
    uint64_t bd = b * d;

    uint64_t mid34 = (bd >> 32) + (bc & 0xffffffff) + (ad & 0xffffffff);

    uint64_t upper64 = ac + (bc >> 32) + (ad >> 32) + (mid34 >> 32);
//  uint64_t lower64 = (mid34 << 32) | (bd & 0xffffffff);

    return upper64;
}

int main( void )
{
    uint64_t x = 0x0000000100000003;
    uint64_t y = 0x55555555ffffffff;

    uint128_t m = x, n = y;
    uint128_t p = m * n;

    uint64_t top = p >> 64;
    printf( "%016llx %016llx\n", top, top64( x, y ) );
}

【讨论】:

    【解决方案2】:

    gcc 和 clang 都支持 128 位整数作为扩展。

    这是一种方法demo

    #include <iostream>
    #include <cstdint>
    
    //https://gcc.gnu.org/onlinedocs/gcc-4.8.1/gcc/_005f_005fint128.html#_005f_005fint128
    using u128 = unsigned __int128;
    using u64  = uint64_t;
    
    void mul64x64( u64 a, u64 b, u64 & hi, u64 & lo ) {
        u128 product = u128(a) * b;
        lo = product;
        hi = product >> 64;
    }
    
    int main()
    {
        u64 hi, lo;
        mul64x64( 40282220, u64{1} << 63, hi, lo );
        std::cout << hi << std::endl;
    }
    

    输出是

    set -x ; clang++ -std=c++11 -O0 -Wall -Werror main.cpp && ./a.out
    + clang++ -std=c++11 -O0 -Wall -Werror main.cpp
    + ./a.out
    20141110
    

    【讨论】:

    • 我可能需要支持比 GCC 4.8 更远的版本,这使得扩展不那么可靠。 ICC 支持也是一项奖励。
    【解决方案3】:

    一点代数永远不会伤害:

    #include <stdint.h>
    
    uint64_t top64(uint64_t x, uint64_t y) {
        uint64_t a = x >> 32, b = x & 0xFFFFFFFF;
        uint64_t c = y >> 32, d = y & 0xFFFFFFFF;
    
        return a * c + ((b * d >> 32) + (a * d) + (b * c)) >> 32 +
        ((((a * d) & 0xFFFFFFFF) + ((b *c) & 0xFFFFFFFF) + ((b * d) >> 32)) >> 32);
    }
    

    【讨论】:

    • 这不太正确,因为低 32 位是通过将 3 个数字相加来计算的,并且该和可能会溢出 1 或 2 位到结果中。
    • @user3386109 是对的;您需要考虑低阶乘积位的执行,以便获得乘高的正确结果。举个简单的例子,以x = y = 2**64-1 为例。你的方法给出了18446744073709551613,而正确的结果是18446744073709551614
    • @gct:即使在移动之前将 ad + bc 相加也是不够的。如果不包括 bd 的高 32 位,就无法得到正确的结果。
    • 我认为这个答案可能会被取消。这是可能的进位:(((a*d)&amp;0xffffffff) + ((b*c)&amp;0xffffffff) + ((b*d)&gt;&gt;32)) &gt;&gt; 32。也许有比这更快的计算进位的方法。
    • @LeeDanielCrocker: 就是将 bd 的高 32 位与 ad 和 bc 的低 32 位相加的进位:1 + 1 + 0xfffffffe = 0x100000000;您似乎在最近的编辑中解决了这个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-17
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-05
    • 1970-01-01
    相关资源
    最近更新 更多