如何准确地对64位整数进行乘法和除法?

时间:2013-08-02 17:11:22

标签: c math biginteger

我有一个C函数:

int64_t fn(int64_t a, int32_t b, int32_t c, int32_t d)
{
    /* should return (a * b * c)/d */   
}

a有可能接近INT64_MAX,但最终结果不会溢出,例如,如果b = 1,则c = d = 40.但是,我无法弄清楚如何计算这个以便我永远不会丢失数据以进行舍入(通过先进行除法)或使中间结果溢出。

如果我可以访问足够大的数据类型以适应a,b和c的整个产品,我只会在该类型中进行数学计算然后截断,但是有一些方法可以在没有大整数的情况下执行此操作?

4 个答案:

答案 0 :(得分:5)

a = q*d + r|r| < |d|(我假设d != 0,否则无论如何计算都没有意义)。然后是(a*b*c)/d = q*b*c + (r*b*c)/d。如果q*b*c溢出,整个计算都会溢出,所以要么你不关心,要么你必须检查溢出。 r*b*c可能仍会溢出,因此我们再次使用相同的方法来避免溢出,

int64_t q = a/d, r = a%d;
int64_t part1 = q*b*c;
int64_t q1 = (r*b)/d, r1 = (r*b)%d;
return part1 + q1*c + (r1*c)/d;

答案 1 :(得分:1)

很容易看出,某些输入会产生无法用返回类型int64_t表示的输出。例如,fn(INT64_MAX, 2, 1, 1)。但是,以下方法应该允许您为任何实际上符合int64_t范围内的输入组合返回正确的答案。

int64_t fn(int64_t a, int32_t b, int32_t c, int32_t d)
{
    /* find the integer and remainder portions of a/d */
    int64_t leftI = a / d;
    int64_t leftR = a % d;

    /* multiply the integer portion of the result by b and c */
    int64_t resultI = leftI * b * c;

    /* multiply the remainder portion by b */
    int64_t resultR = leftR * b;
    resultI = resultI + (resultR / d) * c;

    /* multiply the remainder portion by c */
    resultR = (resultR % d) * c;

    return resultI + (resultR / d);
}

答案 2 :(得分:1)

我建议用a,b和c中的每一个找到d的最大公约数, 在你去的时候分解出共同的因素:

common = gcd(a,d) // You can implement GCD using Euclid's algorithm

a=a/common
d=d/common

common = gcd(b,d)
b=b/common
d=d/common

common = gcd(c,d)
c=c/common
d=d/common

然后计算a*b*c/d,删除所有常见因素。欧几里德的GCD算法 以对数时间运行,所以这应该是相当有效的。

答案 3 :(得分:0)

如果您正在使用x86_64,那么asm支持128位整数:

int64_t fn(uint64_t a, uint64_t b, uint64_t c, uint64_t d) {

    asm (
        "mulq %1\n"          // a *= b
        "movq %%rbx, %%rdx\n"// rbx = upper 64 bit of the multiplication
        "mulq %2\n"          // multiply the lower 64 bits by c
        "push %%rax\n"       // temporarily save the lowest 64 bits on the stack
        "mov %%rcx, %%rdx\n" // rcx = upper 64 bits of the multiplication
        "movq %%rax, %%rbx\n"// 
        "mulq %2\n"          // multiply the upper 64 bits by c
        "addq %%rax, %%rcx\n"// combine the middle 64 bits
        "addcq %%rdx, $0\n"  // transfer carry tp the higest 64 bits if present
        "divq %3\n"          // divide the upper 128 (of 192) bits by d
        "mov %%rbx, %%rax\n" // rbx = result
        "pop %%rax\n"
        "divq %3\n"          // divide remainder:lower 64 bits by d
        : "+a" (a)           // assigns a to rax register as in/out
        , "+b" (b)           // assigns b to rbx register
        : "g" (c)            // assigns c to random register
        , "g" (d)            // assigns d to random register
        : "edx", "rdx"       // tells the compiler that edx/rdx will be used internally, but does not need any input
    );

    // b now holds the upper 64 bit if (a * b * c / d) > UINT64_MAX
    return a;
}

请注意,所有输入整数必须是相同的长度。工作长度将是输入的两倍。仅使用无符号。

x86上的本机divmul指令完全用于双倍长度以允许溢出。遗憾的是,我并不知道使用它们的内在编译器。