如何将64位操作数相乘并获得128位结果?

use*_*486 7 c assembly gcc

对于x64,我可以使用这个:

 {
   uint64_t hi, lo;
  // hi,lo = 64bit x 64bit multiply of c[0] and b[0]

   __asm__("mulq %3\n\t"
    : "=d" (hi),
  "=a" (lo)
    : "%a" (c[0]),
  "rm" (b[0])
    : "cc" );

   a[0] += hi;
   a[1] += lo;
 }
Run Code Online (Sandbox Code Playgroud)

但我想以可移植的方式执行相同的计算.例如,在x86上工作.

Dav*_*nan 13

据我所知,你想要一个64位乘法的便携式纯C实现,输出为128位值,存储在两个64位值中.在这种情况下,这篇文章声称拥有你需要的东西.该代码是为C++编写的.将它变成C代码并不需要太多:

void mult64to128(uint64_t op1, uint64_t op2, uint64_t *hi, uint64_t *lo)
{
    uint64_t u1 = (op1 & 0xffffffff);
    uint64_t v1 = (op2 & 0xffffffff);
    uint64_t t = (u1 * v1);
    uint64_t w3 = (t & 0xffffffff);
    uint64_t k = (t >> 32);

    op1 >>= 32;
    t = (op1 * v1) + k;
    k = (t & 0xffffffff);
    uint64_t w1 = (t >> 32);

    op2 >>= 32;
    t = (u1 * op2) + k;
    k = (t >> 32);

    *hi = (op1 * op2) + w1 + k;
    *lo = (t << 32) + w3;
}
Run Code Online (Sandbox Code Playgroud)


小智 9

由于您有gcc标记,请注意您可以使用gcc的是128位整数类型:

typedef unsigned __int128 uint128_t;
// ...
uint64_t x, y;
// ...
uint128_t result = (uint128_t)x * y;
uint64_t lo = result;
uint64_t hi = result >> 64;
Run Code Online (Sandbox Code Playgroud)


Eas*_*sPi 6

在我看来,接受的解决方案并不是真正的最佳解决方案。

  • 读起来很混乱。
  • 它有一些时髦的携带处理。
  • 它没有利用 64 位算术可用的事实。
  • 它让 ARMv6 这个绝对荒谬的倍增之神感到不高兴。谁用UMAAL谁就不会落后,而且在4条指令中拥有永恒的64位到128位乘法。

开玩笑吧,针对 ARMv6 进行优化比任何其他平台都要好得多,因为它会带来最大的好处。x86 需要一个复杂的例程,这将是一个死胡同优化。

我发现(并在xxHash3中使用)的最好方法是这样,它利用使用宏的多种实现:

它比 x86 上的 mult64to128 慢一点(慢 1-2 条指令),但在 ARMv6 上快得多。

#include <stdint.h>
#ifdef _MSC_VER
#  include <intrin.h>
#endif

/* Prevents a partial vectorization from GCC. */
#if defined(__GNUC__) && !defined(__clang__) && defined(__i386__)
  __attribute__((__target__("no-sse")))
#endif
static uint64_t multiply64to128(uint64_t lhs, uint64_t rhs, uint64_t *high)
{
    /*
     * GCC and Clang usually provide __uint128_t on 64-bit targets,
     * although Clang also defines it on WASM despite having to use
     * builtins for most purposes - including multiplication.
     */
#if defined(__SIZEOF_INT128__) && !defined(__wasm__)
    __uint128_t product = (__uint128_t)lhs * (__uint128_t)rhs;
    *high = (uint64_t)(product >> 64);
    return (uint64_t)(product & 0xFFFFFFFFFFFFFFFF);

    /* Use the _umul128 intrinsic on MSVC x64 to hint for mulq. */
#elif defined(_MSC_VER) && defined(_M_IX64)
#   pragma intrinsic(_umul128)
    /* This intentionally has the same signature. */
    return _umul128(lhs, rhs, high);

#else
    /*
     * Fast yet simple grade school multiply that avoids
     * 64-bit carries with the properties of multiplying by 11
     * and takes advantage of UMAAL on ARMv6 to only need 4
     * calculations.
     */

    /* First calculate all of the cross products. */
    uint64_t lo_lo = (lhs & 0xFFFFFFFF) * (rhs & 0xFFFFFFFF);
    uint64_t hi_lo = (lhs >> 32)        * (rhs & 0xFFFFFFFF);
    uint64_t lo_hi = (lhs & 0xFFFFFFFF) * (rhs >> 32);
    uint64_t hi_hi = (lhs >> 32)        * (rhs >> 32);

    /* Now add the products together. These will never overflow. */
    uint64_t cross = (lo_lo >> 32) + (hi_lo & 0xFFFFFFFF) + lo_hi;
    uint64_t upper = (hi_lo >> 32) + (cross >> 32)        + hi_hi;

    *high = upper;
    return (cross << 32) | (lo_lo & 0xFFFFFFFF);
#endif /* portable */
}
Run Code Online (Sandbox Code Playgroud)

在 ARMv6 上,没有比这更好的了,至少在 Clang 上是这样:

multiply64to128:
        push    {r4, r5, r11, lr}
        umull   r12, r5, r2, r0
        umull   r2, r4, r2, r1
        umaal   r2, r5, r3, r0
        umaal   r4, r5, r3, r1
        ldr     r0, [sp, #16]
        mov     r1, r2
        strd    r4, r5, [r0]
        mov     r0, r12
        pop     {r4, r5, r11, pc}
Run Code Online (Sandbox Code Playgroud)

接受的解决方案会生成一堆addsadc,以及umull由于 instcombine bug 在 Clang 中生成的额外内容。

我在我发布的链接中进一步解释了便携式方法。