大数相乘算法
关于C++中double类型到底可以表示多大的数字,按照我查询的结果是IEEE的标准可以参考。这个改日再说。但是不论值怎么大,都有一个范围。因此针对超过这个范围的值,如果需要做计算,那么就会溢出。
因此有专门的大数乘法计算逻辑,我参考了网上的一些资料,都是按照我们小学学的乘法逻辑来做的计算。
先看看乘法逻辑:
1820 ----------------用变量a表示
× 29 ----------------用变量b表示
------------------
16380
3460
------------------
52780
为了充分体现乘法逻辑,我专门写了个较大的数字乘法。因此我直接总结出乘法逻辑:
- b的个位乘以a的每一位。
- 个位数相乘,查看是否有进位值(carry)。
- 十位数相乘,相乘的结果加上进位值,然后再检查是否可以向百位进位。
- b的十位乘以a的每一位,然后循环2-3步。
因此我们的代码如下:
1 #include <iostream> 2 #include <string> 3 #include <cstring> 4 #include <algorithm> 5 #include <cmath> 6 7 using namespace std; 8 9 string multiply(string a,string b); 10 int main() 11 { 12 string x = "1820"; 13 string y = "29"; 14 string result = multiply(x,y); 15 cout << result << endl; 16 return 0; 17 } 18 19 string multiply(string a,string b) 20 { 21 int a_length = a.size(); 22 int b_length = b.size(); 23 int *iresult = new int[a_length+b_length+1]; 24 memset(iresult,0,sizeof(int)*(a_length+b_length)); 25 int avalue=0; 26 int bvalue=0; 27 int carry=0; 28 int temp = 0; 29 int i,j; 30 for(j=b_length-1;j>=0;j--) 31 { 32 bvalue = b[j]-48; 33 for(i=a_length-1;i>=0;i--) 34 { 35 avalue = a[i]-48; 36 temp = avalue*bvalue+carry + iresult[i+j+2]; 37 iresult[i+j+2] = temp%10; 38 carry=temp/10; 39 } 40 if(carry!=0) 41 { 42 iresult[i+j+2] = carry; 43 carry = 0; 44 } 45 } 46 if(carry!=0) 47 { 48 iresult[i+j+2] = carry; 49 carry = 0; 50 } 51 52 string result=""; 53 int pos=0; 54 for(pos=0;pos<a_length+b_length+1;pos++) 55 { 56 if(iresult[pos]!=0)break; 57 } 58 59 for(;pos<a_length+b_length+1;pos++) 60 { 61 result = result + (char)(iresult[pos]+48); 62 } 63 return result; 64 }
代码解析:
第9行:string multiply(string a,string b); 就是大数相乘函数。因为数字太大会溢出,所以用string类型来表示数字。
第21,22 行分别算出两个数字的位数。
第23行用一个整型数组来记录大数相乘后的结果iresult。
第24行先初始化这个数组iresult,用0替换数组每个元素。注意,这个必须有。如果不是0的话,会导致第一次相乘后的结果。此处注意memset的用法。memset 是按照字节来初始化的。因此需要乘sizeof(int)。
第25,26行声明变量来表示两个大数的每一位数字。
第27行声明carry变量用来记录每一次乘法的进位值。
第28行声明temp变量来记录每一次做乘法的结果。
第29行声明i,j变量用来遍历大数的每一位。
第30行开始做乘法:
1) 这里我们需要从后向前遍历,因为个位数在最后。其他很多资料都是说,需要将大数做一个reverse,这样可以从前向后遍历。但我觉得这种方式不符合我们做乘法的习惯。
2) 我们要计算a*b,那么我这里在外循环遍历b,用b的每一位乘以a的每一位。
第32行我用方式解释:
char x = '5';
int y = x - 48。
y的结果是5。
也就是说字符'5' 和数字5 之间相差48. 这个是ASCII编码。其他数字也一样。
下面就到了关键点:
第36 行就是b的每一位数字和a的每一位数字相乘。同时加上从低位进位来的数字,并且加上上一轮相乘的结果值。
比如1820×29。 1820×9=16380. 1820×2的时候,我们不断需要记录从低位的进位值,还要考虑对应位置加上16380 对应的值。理解了这一步。后面就完事大吉了。
第37,38 行分别用除10,%10 来计算该位应该保留的值,以及进位值。
第40行,是因为存在最高位进位,但是最高位也是循环边界,所以需要单独在内循环外来单独计算。最高位进位后,需要把进位值清零。
第46行同理。
52行以后,我们从数组下标0开始,找到真正的数字最高位,最高位之前都为0,所以我们需要跳过。然后从最高位开始将这些数字填充到字符串中。至此,计算结束。
我忽然发现里面其实还有一点错误....就是数组没有释放...