我需要将两个带符号的64位整数a
和b
相乘,然后将(128位)结果移位到带符号的64位整数。最快的方法是什么?
我的64位整数实际上代表具有fmt
小数位的定点数。选择fmt
以便a * b >> fmt
不会溢出,例如abs(a) < 64<<fmt
和abs(b) < 2<<fmt
fmt==56
永远不会溢出64位,因为最终结果将是< 128<<fmt
因此适合int64。
我想这样做的原因是以定点格式快速准确地评估((((c5*x + c4)*x + c3)*x + c2)*x + c1)*x + c0
形式的五次多项式,每个数字都是带有fmt
小数的带符号的64位定点数位。我正在寻找实现这一目标的最有效方法。
答案 0 :(得分:7)
作为该问题的评论者指出,这最容易通过依赖于机器的代码而不是通过可移植代码有效地完成。提问者声明主平台是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;
}