【问题标题】:Fastest way to multiply two 64-bit ints to 128-bit then >> to 64-bit? [duplicate]将两个 64 位整数乘以 128 位然后 >> 乘以 64 位的最快方法? [复制]
【发布时间】:2015-10-17 15:01:10
【问题描述】:

我需要将两个有符号的 64 位整数 ab 相乘,然后将(128 位)结果转换为有符号的 64 位整数。最快的方法是什么?

我的 64 位整数实际上用fmt 小数位表示定点数。选择fmt 是为了使a * b >> fmt 不会溢出,例如abs(a) < 64<<fmtabs(b) < 2<<fmtfmt==56 在64 位中永远不会溢出,因为最终结果将是< 128<<fmt,因此适合int64。

我想这样做的原因是快速准确地以定点格式计算 ((((c5*x + c4)*x + c3)*x + c2)*x + c1)*x + c0 形式的五次多项式,每个数字都是带符号的 64 位定点数和 fmt 小数位。我正在寻找实现这一目标的最有效方法。

【问题讨论】:

  • 您的问题陈述表明您可能已经尝试过实现。如果是这样,您可以发布您的代码吗?
  • 我怀疑最快的方法就是这样做(假设您有一个可以利用的现有 int128 实现)。
  • @ryyker 我没有,我只用 int32、double 和 __float128 尝试过同样的事情,从来没有用过 int64,所以我从来不用处理 int128 结果。
  • @Oliver Charlesworth 这应该是可移植的代码,我不知道有一个可以广泛使用的 int128 实现。我认为不需要 int128 类型的东西是可行的,因为毕竟编译器可能做的事情是我可以在没有 int128 类型的情况下做的事情,对吧?我认为我需要转换以获得 int64 结果可能允许一些聪明的技巧。
  • 了解 ISA 的一些信息会很有帮助。写成非便携式的通常要容易得多。

标签: c integer fixed-point int128


【解决方案1】:

正如该问题的评论者所指出的,这最容易通过机器相关代码而不是可移植代码有效地完成。提问者说主平台是 x86_64,并且有一个用于执行 64 ✕ 64 → 128 位乘法的内置指令。这很容易使用一小块内联汇编来访问。请注意,内联汇编的细节可能会因编译器而有所不同,以下代码是使用英特尔 C/C++ 编译器构建的。

#include <stdint.h>

/* compute mul_wide (a, b) >> s, for s in [0,63] */
int64_t mulshift (int64_t a, int64_t b, int s)
{
    int64_t res;
    __asm__ (
        "movq  %1, %%rax;\n\t"          // rax = a
        "movl  %3, %%ecx;\n\t"          // ecx = s
        "imulq %2;\n\t"                 // rdx:rax = a * b
        "shrdq %%cl, %%rdx, %%rax;\n\t" // rax = int64_t (rdx:rax >> s)
        "movq  %%rax, %0;\n\t"          // res = rax
        : "=rm" (res)
        : "rm"(a), "rm"(b), "rm"(s)
        : "%rax", "%rdx", "%ecx");
    return res;
}

与上述代码等效的可移植 C99 如下所示。我已经针对内联汇编版本对此进行了广泛的测试,没有发现不匹配的情况。

void umul64wide (uint64_t a, uint64_t b, uint64_t *hi, uint64_t *lo)
{
    uint64_t a_lo = (uint64_t)(uint32_t)a;
    uint64_t a_hi = a >> 32;
    uint64_t b_lo = (uint64_t)(uint32_t)b;
    uint64_t b_hi = b >> 32;

    uint64_t p0 = a_lo * b_lo;
    uint64_t p1 = a_lo * b_hi;
    uint64_t p2 = a_hi * b_lo;
    uint64_t p3 = a_hi * b_hi;

    uint32_t cy = (uint32_t)(((p0 >> 32) + (uint32_t)p1 + (uint32_t)p2) >> 32);

    *lo = p0 + (p1 << 32) + (p2 << 32);
    *hi = p3 + (p1 >> 32) + (p2 >> 32) + cy;
}

void mul64wide (int64_t a, int64_t b, int64_t *hi, int64_t *lo)
{
    umul64wide ((uint64_t)a, (uint64_t)b, (uint64_t *)hi, (uint64_t *)lo);
    if (a < 0LL) *hi -= b;
    if (b < 0LL) *hi -= a;
}

/* compute mul_wide (a, b) >> s, for s in [0,63] */
int64_t mulshift (int64_t a, int64_t b, int s)
{
    int64_t res;
    int64_t hi, lo;
    mul64wide (a, b, &hi, &lo);
    if (s) {
        res = ((uint64_t)hi << (64 - s)) | ((uint64_t)lo >> s);
    } else {
        res = lo;
    }
    return res;
}

【讨论】:

  • 即将通过组合 32x32->64 位乘法器来实现。没有 imulq 指令。验证您的解决方案 - 它按预期工作
  • 太棒了,谢谢!现在我只需要一个可移植的后备(对于仍然有些必要的 32 位构建或最终的其他平台)来配合它。
  • 让我看看在可移植的后备代码方面我能做些什么。应该不会太难。
  • 不要使用内联汇编,试试这个:#include uint64_t multophalf_intrinsic(uint64_t a, uint64_t b) { unsigned long long hi = 0; _mulx_u64(a, b, &hi);回嗨; }
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-07
  • 2014-08-25
  • 1970-01-01
  • 2013-07-25
  • 1970-01-01
  • 2012-03-16
  • 2015-05-02
相关资源
最近更新 更多