我用 C++ 编写了一个 BigInteger 类,它应该能够对任何大小的所有数字进行运算。目前,我正在尝试通过比较现有算法并测试它们最适合哪些位数来实现非常快速的乘法方法,但我遇到了非常意外的结果。我尝试进行 20 次 500 位数字的乘法,并对它们进行计时。结果是这样的:
karatsuba:
14.178 seconds
long multiplication:
0.879 seconds
维基百科告诉我
由此可见,对于足够大的 n,Karatsuba 算法将比普通乘法执行更少的移位和个位数加法,尽管其基本步骤比直接公式使用更多的加法和移位。然而,对于较小的 n 值,额外的移位和加法操作可能会使其运行速度比普通方法慢。正回报点取决于计算机平台和环境。根据经验,当被乘数长于 320-640 位时,Karatsuba 通常会更快。
由于我的数字至少有 1500 位长,这是非常意外的,因为维基百科说 karatsuba 应该运行得更快。我相信我的问题可能出在我的加法算法中,但我不知道如何使其更快,因为它已经在 O(n) 中运行。我将在下面发布我的代码,以便您可以检查我的实现。我将省略不相关的部分。
我也在想,也许我使用的结构不是最好的。我用小端表示每个数据段。例如,如果我将数字 123456789101112 存储到长度为 3 的数据段中,它将如下所示:
{112,101,789,456,123}
所以这就是为什么我现在要问实现 BigInteger 类的最佳结构和最佳方法是什么?为什么 karasuba 算法比长乘法慢?
这是我的代码:(对于长度我很抱歉)
using namespace std;
bool __longmult=true;
bool __karatsuba=false;
struct BigInt {
public:
vector <int> digits;
BigInt(const char * number) {
//constructor is not relevant
}
BigInt() {}
void BigInt::operator = (BigInt a) {
digits=a.digits;
}
friend BigInt operator + (BigInt,BigInt);
friend BigInt operator * (BigInt,BigInt);
friend ostream& operator << (ostream&,BigInt);
};
BigInt operator + (BigInt a,BigInt b) {
if (b.digits.size()>a.digits.size()) {
a.digits.swap(b.digits); //make sure a has more or equal amount of digits than b
}
int carry=0;
for (unsigned int i=0;i<a.digits.size();i++) {
int sum;
if (i<b.digits.size()) {
sum=b.digits[i]+a.digits[i]+carry;
} else if (carry==1) {
sum=a.digits[i]+carry;
} else {
break; // if carry is 0 and no more digits in b are left then we are done already
}
if (sum>=1000000000) {
a.digits[i]=sum-1000000000;
carry=1;
} else {
a.digits[i]=sum;
carry=0;
}
}
if (carry) {
a.digits.push_back(1);
}
return a;
}
BigInt operator * (BigInt a,BigInt b) {
if (__longmult) {
BigInt res;
for (unsigned int i=0;i<b.digits.size();i++) {
BigInt temp;
temp.digits.insert(temp.digits.end(),i,0); //shift to left for i 'digits'
int carry=0;
for (unsigned int j=0;j<a.digits.size();j++) {
long long prod=b.digits[i];
prod*=a.digits[j];
prod+=carry;
int t=prod%1000000000;
temp.digits.push_back(t);
carry=(prod-t)/1000000000;
}
if (carry>0) {
temp.digits.push_back(carry);
}
res+=temp;
}
return res;
} else if (__karatsuba) {
BigInt res;
BigInt a1,a0,b1,b0;
assert(a.digits.size()>0 && b.digits.size()>0);
while (a.digits.size()!=b.digits.size()) { //add zeroes for equal size
if (a.digits.size()>b.digits.size()) {
b.digits.push_back(0);
} else {
a.digits.push_back(0);
}
}
if (a.digits.size()==1) {
long long prod=a.digits[0];
prod*=b.digits[0];
res=prod;//conversion from long long to BigInt runs in constant time
return res;
} else {
for (unsigned int i=0;i<a.digits.size();i++) {
if (i<(a.digits.size()+(a.digits.size()&1))/2) { //split the number in 2 equal parts
a0.digits.push_back(a.digits[i]);
b0.digits.push_back(b.digits[i]);
} else {
a1.digits.push_back(a.digits[i]);
b1.digits.push_back(b.digits[i]);
}
}
}
BigInt z2=a1*b1;
BigInt z0=a0*b0;
BigInt z1 = (a1 + a0)*(b1 + b0) - z2 - z0;
if (z2==0 && z1==0) {
res=z0;
} else if (z2==0) {
z1.digits.insert(z1.digits.begin(),a0.digits.size(),0);
res=z1+z0;
} else {
z1.digits.insert(z1.digits.begin(),a0.digits.size(),0);
z2.digits.insert(z2.digits.begin(),2*a0.digits.size(),0);
res=z2+z1+z0;
}
return res;
}
}
int main() {
clock_t start, end;
BigInt a("984561231354629875468546546534125215534125215634987498548489456125215421563498749854848945612385663498749854848945612521542156349874985484894561238561698774565123165221393856169877456512316552156349874985484894561238561698774565123165221392213935215634987498548489456123856169877456512316522139521563498749854848945612385616987745651231652213949651465123151354686324848945612385616987745651231652213949651465123151354684132319321005482265341252156349874985484894561252154215634987498548489456123856264596162131");
BigInt b("453412521563498749853412521563498749854848945612521542156349874985484894561238565484894561252154215634987498548489456123856848945612385616935462987546854521563498749854848945653412521563498749854848945612521542156349874985484894561238561238754579785616987745651231652213965465341235215634987495215634987498548489456123856169877456512316522139854848774565123165223546298754685465465341235215634987498548354629875468546546534123521563498749854844139496514651231513546298754685465465341235215634987498548435468");
__longmult=false;
__karatsuba=true;
start=clock();
for (int i=0;i<20;i++) {
a*b;
}
end=clock();
printf("\nTook %f seconds\n", (double)(end-start)/CLOCKS_PER_SEC);
__longmult=true;
__karatsuba=false;
start=clock();
for (int i=0;i<20;i++) {
a*b;
}
end=clock();
printf("\nTook %f seconds\n", (double)(end-start)/CLOCKS_PER_SEC);
return 0;
}