今天有空研究下大数相乘,按照个人习惯在看别人的算法之前,我还是习惯自己先研究一番。
先解释下何为大数相乘,所以大数相乘即由于乘数和被乘数以及相乘结果超过语言级自带的范围时,或者有严格的精度要求时的计算。例如2个30多位的数字相乘。
很多人给出的大致算法是使用程序模拟手工计算乘法,这里主要涉及频繁的进位,对齐。我仔细分析了下发现还是有很多技巧可以提升的。下面是具体的分析过程:
假设:大数在2个数组中存储,而且顺序是一致的,即高位在前。
数组1:char A[ ],数组2:cahr B[ ],
m=strlen(a),n=strlen(b)
最初的想法是:
sum
for(i = m-1; i >= 0; i--)
for(j = n-1 ; j >= 0; j--)
sum = A[i] * B[j] * 10^(i+j) + sum // A*B* 进制
问题1:没有这样的数据类型 sum,能够存放足够大的数字
解决方法: 忽略进制,把每一位看成一个字符,只要保证其所在位置正确即可。
我们以一个二位数组记录临时结果,至于进位计算的过程可以不考虑,在最后统一进位。这一点其实很好理解,
例如2点60分,虽然不属于常规的表达方式,但是在逻辑上同3点是一样的,同样 101,可以表达为:9*10+11*1 。
这个二维数组横坐标是 数位,纵坐标是 每一位的数值。
由此引申出另外2个变量:
相乘后结果的位数最大值:m+n = X
关于每一位的最大值,由于每一位相乘最大值不超过100,而最多出现的次数是 max(m,n) = Y,那么这个数最大值为:100 * Y
/* int 二维数组,不太合适,这部分内容可以忽略
因此这个数组定义为:unsigned T[ X ][ M ( 100 * Y)] 备注: M:需要int的最小位数 N
由于1个int(32位)能存放的最大整数为:4.294967295×10⁹ 已经很大了,我们可以简化处理下:
#define MAX_VERTICAL 1,于是二维数组被定义为:unsigned T[X][MAX_VERTICAL]
*/
/* 再次否定 不方便运算
这个数组定义为: char T[ X ][ log(2, 100*Y)]
简化下:#define MAX_VERTICAL 30,于是二维数组被定义为:unsigned T[X][MAX_VERTICAL]
*/
由于1个int(32位)能存放的最大整数为:4.294967295×10⁹ 已经很大了,我们使用1个int 型一维数组就足够了,
而且方便计算。
我们再分析下,每2个数位相乘对应到数组的第几个位置,可以简单归纳下不难得出: i + j 就对应的列上。
因此,我们进一步优化代码为:
//初始化 T
for(i = m-1; i >= 0; i--)
for(j = n-1 ; j >= 0; j--)
T[ i+j ] = T[ i+j ] + A[ i ] * B[ j ] ;
循环结束,我们得到一个非常规表达的结果,即1个3位数为 1*100+ 21*10+33*1,接下来就是最这个结果进行常规化。
我们可以从低位开始对每位对10求余,余数即当前位的值;求商,商即要进位的值;
我们按照相反顺序把结果打印出来或者存储到字符中即是我们想要的结果。
下面是完整的代码(已经编译并测试通过):
#include <stdio.h> int huge_num_plus(const char *A, const char *B, char *output) { int m =strlen(A), n =strlen(B); int i=0,j=0; short A_T[ m ], B_T[ n ]; //用来将char型转化成int型 int mid_rec[ m+n ]; short tmp=0; //将字符型转化为数字型 for(i=0; i<m; i++) A_T[i] = A[i] - '0'; for(j=0; j<n; j++) B_T[j] = B[j] - '0'; //初始化 for(j=0; j<m+n; j++) mid_rec[j] = 0; //迭代计算,模拟手工乘法计算 for(i = m-1; i >= 0; i--) for(j = n-1 ; j >= 0; j--) mid_rec[ i+j+1 ] = mid_rec[ i+j+1 ] + A_T[ i ] * B_T[ j ]; //常规化:将类似“2点61分”转化为“3点01分” for(i = m+n-1; i > 0; i--){ tmp = mid_rec[i]/10; mid_rec[i] = mid_rec[i]%10; mid_rec[i-1] = mid_rec[i-1] + tmp; } /* for test printf("mid_rec:\n"); for(i=0; i<m+n; i++) printf("%d", mid_rec[i]); printf("\n"); */ //除去第1位可能存在的0 for(j=0, i=((mid_rec[0] == 0)?1:0); i<m+n ; j++,i++) output[j] = (mid_rec[i] + '0'); //将数字转化为char型 output[ j+1 ] = '\0'; printf("output is %s\n", output); return 0; } int main(int argc, char** argv) { if(argc < 3){ printf("uasge ./huge_num_plus Multiplicand Multiplier\n"); exit(1); } char result[100]; memset(result, 0, sizeof(result)); huge_num_plus(argv[1], argv[2], result); printf("(%s * %s) = %s \n ", argv[1], argv[2], result); return 0; }