C++中2个64位数字的乘法

Posted

技术标签:

【中文标题】C++中2个64位数字的乘法【英文标题】:Multiplication of 2 64-digit numbers in C++ 【发布时间】:2017-01-18 11:43:50 【问题描述】:

我已经实现了 karatsuba 乘法算法。我想以这种方式改进它,我可以将 2 个 64 位数字相乘,但我不知道该怎么做。我得到一个提示,这两个数字都包含数字的个数,它是 2 的幂,但它什么也没告诉我。你能给出任何其他提示吗?数学提示或算法改进提示。

#include <iostream>
#include <math.h>
using namespace std;

int getLength(long long value);
long long multiply(long long x, long long y);

int getLength(long long value)

    int counter = 0;
    while (value != 0)
    
        counter++;
        value /= 10;
    
    return counter;


long long multiply(long long x, long long y)

    int xLength = getLength(x);
    int yLength = getLength(y);

    // the bigger of the two lengths
    int N = (int)(fmax(xLength, yLength));

    // if the max length is small it's faster to just flat out multiply the two nums
    if (N < 10)
        return x * y;

    //max length divided and rounded up
    N = (N / 2) + (N % 2);

    long long multiplier = pow(10, N);

    long long b = x / multiplier;
    long long a = x - (b * multiplier);
    long long d = y / multiplier;
    long long c = y - (d * N);

    long long z0 = multiply(a, c);
    long long z1 = multiply(a + b, c + d);
    long long z2 = multiply(b, d);


    return z0 + ((z1 - z0 - z2) * multiplier) + (z2 * (long long)(pow(10, 2 * N)));



int main()

    long long a;
    long long b;
    cin >> a;
    cout << '\n';
    cin >> b;
    cout << '\n' << multiply(a, b) << endl;
    return 0;

【问题讨论】:

这里还有一个提示:使用pow() will give you wrong answers 因为floating point math is broken。 long long 将无法存储两个 64 位数字相乘的结果。事实上,它甚至不能保存一个 64 位数字。您将有整数溢出,这会导致未定义的行为。您将需要自己的 BigNumber 类。 我知道但我无法改进算法,这就是我寻求帮助的原因。也许是想法。当我得到提示时,也许有一个数学技巧:64 = 2^6 64 位二进制数还是 64 位十进制数? 【参考方案1】:

这里有一个提示:

(A + kB) * (C + kD) = AC + k(BC + AD) + k^2(BD)

如果k 是您保存数字的底数的幂,这将很有帮助。例如,如果k 是 1'000'000'000 并且您的数字是 base-10,那么乘以 @ 987654324@ 是通过简单地移动数字(添加零)来完成的。

无论如何,请考虑将您的 64 位数字分成两部分,每部分 32 位,然后像上面那样进行数学运算。要计算 ACBCADBD,您需要将一对 32 位数字相乘,可以类似地完成。

由于您的位数是 2 的幂,您可以继续将您的数字分成两半,直到您达到可管理的数字大小(例如 1 位数字)。

顺便说一句,从您的问题中不清楚您是在谈论 64 位还是 64 位十进制数字。如果您要查找的只是 64 位数字相乘,请执行以下操作:

// I haven't actually run this code, so...

typedef unsigned long long u64;

u64 high32 (u64 x) return x >> 32;
u64 low32  (u64 x) return x & 0xFFFFFFFF;

u64 add_with_carry (u64 a, u64 b, u64 * carry)

    u64 result = a + b;
    *carry = (result < a ? 1 : 0);
    return result;


void mul (u64 a, u64 b, u64 * result_low, u64 * result_high)

    u64 a0 = low32(a), a1 = high32(a);
    u64 b0 = low32(b), b1 = high32(b);

    u64 a0b0 = a0 * b0;
    u64 a0b1 = a0 * b1;
    u64 a1b0 = a1 * b0;
    u64 a1b1 = a1 * b1;

    u64 c0 = 0, c1 = 0;
    u64 mid_part = add_with_carry(a0b1, a1b0, &c1);

    *result_low  = add_with_carry(a0b0, (low32(mid_part) << 32, &c0);
    *result_high = high32(mid_part) + a1b1 + (c1 << 32) + c0; // this won't overflow

此实现与上面概述的想法相同。由于在标准 C/C++ 中,我们可以在乘法结果中拥有的最大有意义的位数是 64,因此我们一次只能将两个 32 位数字相乘。这就是我们在这里所做的。

最终结果将是 128 位,我们以两个无符号 64 位数字返回。我们正在执行 64 位乘 64 位乘法,通过执行 4 次 32 位乘法和一些加法。

作为旁注,这是编写汇编程序通常比 C 更容易的少数情况之一。例如,在 x64 汇编程序中,这实际上是一条 mul 指令,它将两个 64 位无符号数相乘整数并在两个 64 位寄存器中返回 128 位结果。

即使您没有 64 位到 128 位的乘法指令,在汇编中编写它仍然更容易(因为 adc 或类似的指令,例如上面的整个代码只有两个 movs ,四个muls,四个adds,和四个adcs 在普通x86汇编中。)即使你不想用汇编写,你应该检查你的编译器的“内在”。 /em> 它可能有一个用于大乘法(但您将依赖于平台。)

【讨论】:

【参考方案2】:

要将 Karatsuba 或任何其他乘法应用于较低位宽的算术,您需要将您的数字分成更小的“数字”。首先,您需要访问这些“数字”,所以这里是如何做到的:

你有号码1234,想把它分成10^1数字所以

1234 = 1*1000 + 2*100 + 3*10 + 4

你可以这样获取数字:

x=1234;
a0=x%10; x/=10; // 4
a1=x%10; x/=10; // 3
a2=x%10; x/=10; // 2
a3=x%10; x/=10; // 1

如果你想要10^2 数字,那么:

x=1234;
a0=x%100; x/=100; // 34
a1=x%100; x/=100; // 12

现在的问题是,要做到这一点,您需要对您没有的完整数字进行除法。如果您将数字作为字符串,那么它很容易完成,但假设您没有。计算机基于二进制计算,因此最好使用 2 的幂作为“数字”的基础,因此:

x = 1234 = 0100 1101 0010 bin

现在,如果我们想要例如 2^4=16 基本数字,那么:

a0=x%16; x/=16; // 0010
a1=x%16; x/=16; // 1101
a2=x%16; x/=16; // 0100

现在如果你意识到除以 2 的幂只是右移,余数可以表示为 AND 那么:

a0=x&15; x>>=4; // 0010
a1=x&15; x>>=4; // 1101
a2=x&15; x>>=4; // 0100

位移位可以堆叠到任何位宽数字,所以现在你得到了你需要的一切。但这还不是全部,如果您选择“数字”,例如2^8,即BYTE,那么您可以使用指针来代替,例如:

DWORD x=0x12345678; // 32 bit number
BYTE *db=(BYTE*)(&x); // 8bit pointer that points to x

a0=db[0]; // 0x78
a1=db[1]; // 0x56
a2=db[2]; // 0x34
a3=db[3]; // 0x12

因此您可以直接访问数字或从数字重构 x:

DWORD x; // 32 bit number
BYTE *db=(BYTE*)(&x); // 8bit pointer that points to x
db[0]=0x78;
db[1]=0x56;
db[2]=0x34;
db[3]=0x12;
// here x should be 0x12345678

请注意,顺序取决于平台 MSB 或 LSB 第一顺序。现在您可以应用乘法。例如,在 16 位乘法上完成 32*32=64 位是这样使用天真的 O(n^2) 方法完成的:

x(a0+a1<<16) * y(b0+b1<<16) = a0*b0 + a0*b1<<16 + a1*b0<<16 + a1*b1<<32

其中 a0,a1,b0,b1 是操作数的位数。请注意,每个 ai*bj 乘法的结果是 2 位宽,因此您需要将其拆分为数字并存储到位移位寻址的结果数字中。请注意,添加可能会导致溢出到更高的数字。要处理这个问题,您需要使用至少两倍的算术宽度进行加法(16*16 位 mul -> 32 位加法)或使用进位标志。可悲的是,除了在 C++ 中使用汇编之外,您无法访问进位标志。幸运的是可以模拟看到:

Cant make value propagate through carry

现在您可以构建 Karatsuba 或更高级的乘法以了解更多信息,请参阅:

Fast bignum square computation

【讨论】:

以上是关于C++中2个64位数字的乘法的主要内容,如果未能解决你的问题,请参考以下文章

乘法和比较大数字

生成C ++中N个数字中的所有R位数字(组合,迭代)?

java面试之位运算(如何不通过第三方变量交换两个数字,效率最高的乘法运算)

变量和基本类型C++

L1-080 乘法口诀数列 (20 分)签到题

仅当至少一个数字为 2 位时才能使用大数字(字符串)函数进行乘法运算