我实施了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;
}
答案 0 :(得分:4)
这是一个提示:
(A + kB) * (C + kD) = AC + k(BC + AD) + k^2(BD)
如果k
是你保持数字的基础的力量,这会有所帮助。例如,如果k
是1'000'000'000且你的数字是10的基数,那么k
的乘法是通过简单地移动数字(加零)来完成的。
无论如何,考虑将你的64位数字分为两部分,每部分32位,并按上述方式进行数学运算。要计算AC
,BC
,AD
和BD
,您要乘以一对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;
if (result < a) // and/or b?
*carry = 1;
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位数字返回。我们通过4次32位乘法和一些加法进行64位乘64位乘法运算。
作为旁注,这是少数情况之一,写这个程序集通常比C更容易。例如,在x64程序集中,这实际上是两个mov
s,四个{{1} } s,四个mul
和四个add
s(这只是使用基本的CPU指令。)
答案 1 :(得分:1)
要在较低位宽的算术中应用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 ++中使用汇编时,您无法访问Carry标志。幸运的是,可以模拟看到:
现在您可以构建您的Karatsuba或更高级的乘法以获取更多信息,请参阅: