前言:
在计算机中数字表示的范围是有限制的,比如我们熟知的 int、float、double 等数据类型所能表示的范围都是有限的,如果我们要对位数达到几十位、几百位、上千位的大整数进行计算,这些数据类型显然不能满足我们的要求,因此我们需要通过算法来实现这些功能。
1、大数加法
两个大数我们可以用数组来保存,然后在数组中逐位进行相加,再判断该位相加后是否需要进位,为了方便计算,我们将数字的低位放在数组的前面,高位放在后面。
下面是两个正的大整数相加算法的C语言参考代码:
1 #include<stdio.h> 2 #include<string.h> 3 4 #define MAX 1000 // 大数的最大位数 5 6 7 /* 8 大数加法 9 参数: 10 num1为第一个大数,用字符数组保存 11 num2为第二个大数 12 sum数组保存相加的结果 即:num1+num2=sum 13 返回值:返回数组sum的有效长度,即计算结果的位数 14 */ 15 int Addition(char num1[], char num2[], int sum[]) 16 { 17 int i, j, len; 18 int n2[MAX] = {0}; 19 int len1 = strlen (num1); // 计算数组num1的长度,即大数的位数 20 int len2 = strlen (num2); // 计算数组num2的长度,即大数的位数 21 22 len = len1>len2 ? len1 : len2; // 获取较大的位数 23 //将num1字符数组的数字字符转换为整型数字,且逆向保存在整型数组sum中,即低位在前,高位在后 24 for (i = len1-1, j = 0; i >= 0; i--, j++) 25 sum[j] = num1[i] - '0'; 26 // 转换第二个数 27 for (i = len2-1, j = 0; i >= 0; i--, j++) 28 n2[j] = num2[i] - '0'; 29 // 将两个大数相加 30 for (i = 0; i <= len; i++) 31 { 32 sum[i] += n2[i]; // 两个数从低位开始相加 33 if (sum[i] > 9) // 判断是否有进位 34 { // 进位 35 sum[i] -= 10; 36 sum[i+1]++; 37 } 38 } 39 if(sum[len] != 0) // 判断最高位是否有进位 40 len++; 41 return len; // 返回和的位数 42 } 43 44 int main() 45 { 46 int i, len; 47 int sum[MAX] = {0}; // 存放计算的结果,低位在前,高位在后,即sum[0]是低位 48 char num1[] = "1234567891234567891234"; // 第一个大数 49 char num2[] = "2345678912345678913345"; // 第二个大数 50 len = Addition(num1, num2, sum); // 两数相加 51 printf("%s + %s = ", num1, num2); 52 // 反向输出求和结果 53 for (i = len-1; i >= 0; i--) 54 printf("%d", sum[i]); 55 printf(" "); 56 return 0; 57 }
2、大数减法
相减算法也是从低位开始减的。先要判断被减数和减数哪一个位数长,若被减数位数长是正常的减法;若减数位数长,则用被减数减去减数,最后还要加上负号;当两数位数长度相等时,最好比较哪一个数字大,否则负号处理会很繁琐;处理每一项时要,如果前一位相减有借位,就先减去上一位的借位,无则不减,再去判断是否能够减开被减数,如果减不开,就要借位后再去减,同时置借位为1,否则置借位为0。
下面是C语言参考代码:
1 #include<stdio.h> 2 #include<string.h> 3 4 #define MAX 1000 // 大数的最大位数 5 6 7 /* 8 大数减法 9 参数: 10 num1为被减数,用字符数组保存 11 num2为减数 12 sum数组保存相减的结果 即:num1-num2=sum 13 返回值:返回数组sum的有效长度,即计算结果的位数 14 */ 15 int Subtraction(char num1[], char num2[], int sum[]) 16 { 17 int i, j, len, blag; 18 char *temp; 19 int n2[MAX] = {0}; 20 int len1 = strlen(num1); // 计算数组num1的长度,即大数的位数 21 int len2 = strlen(num2); // 计算数组num2的长度,即大数的位数 22 23 // 在进行减法之前要进行一些预处理 24 blag = 0; // 为0表示结果是正整数,为1表示结果是负整数 25 if(len1 < len2) // 如果被减数位数小于减数 26 { 27 blag = 1; // 标记结果为负数 28 // 交换两个数,便于计算 29 temp = num1; 30 num1 = num2; 31 num2 = temp; 32 len = len1; 33 len1 = len2; 34 len2 = len; 35 } 36 else if(len1 ==len2) // 如果被减数的位数等于减数的位数 37 { 38 // 判断哪个数大 39 for(i = 0; i < len1; i++) 40 { 41 if(num1[i] == num2[i]) 42 continue; 43 if(num1[i] > num2[i]) 44 { 45 blag = 0; // 标记结果为正数 46 break; 47 } 48 else 49 { 50 blag = 1; // 标记结果为负数 51 // 交换两个数,便于计算 52 temp = num1; 53 num1 = num2; 54 num2 = temp; 55 break; 56 } 57 } 58 } 59 len = len1>len2 ? len1 : len2; // 获取较大的位数 60 //将num1字符数组的数字转换为整型数且逆向保存在整型数组sum中,即低位在前,高位在后 61 for (i = len1-1, j = 0; i >= 0; i--, j++) 62 sum[j] = num1[i] - '0'; 63 // 转换第二个数 64 for (i = len2-1, j = 0; i >= 0; i--, j++) 65 n2[j] = num2[i] - '0'; 66 // 将两个大数相减 67 for (i = 0; i <= len; i++) 68 { 69 sum[i] = sum[i] - n2[i]; // 两个数从低位开始相减 70 if (sum[i] < 0) // 判断是否有借位 71 { // 借位 72 sum[i] += 10; 73 sum[i+1]--; 74 } 75 } 76 // 计算结果长度 77 for (i = len1-1; i>=0 && sum[i] == 0; i--) 78 ; 79 len = i+1; 80 if(blag==1) 81 { 82 sum[len] = -1; // 在高位添加一个-1表示负数 83 len++; 84 } 85 return len; // 返回结果的位数 86 } 87 88 int main() 89 { 90 int i, len; 91 int sum[MAX] = {0}; // 存放计算的结果,低位在前,高位在后,即sum[0]是低位 92 char num1[] = "987654321987654321"; // 第一个大数 93 char num2[] = "123456789123456789"; // 第二个大数 94 len = Subtraction(num1, num2, sum); // 两数相减 95 // 输出结果 96 printf("%s - %s = ", num1, num2); 97 if(sum[i=len-1] < 0) // 根据高位是否是-1判断是否是负数 98 { 99 printf("-"); // 输出负号 100 i--; 101 } 102 for (; i >= 0; i--) 103 printf("%d", sum[i]); 104 printf(" "); 105 return 0; 106 }
3、大数乘法
首先说一下乘法计算的算法,从低位向高位乘,在竖式计算中,我们是将乘数第一位与被乘数的每一位相乘,记录结果,之后,用第二位相乘,记录结果并且左移一位,以此类推,直到计算完最后一位,再将各项结果相加,得出最后结果。
计算的过程基本上和小学生列竖式做乘法相同。为了编程方便,并不急于处理进位,而是将进位问题留待最后统一处理。
总结一个规律: 即一个数的第i 位和另一个数的第j 位相乘所得的数,一定是要累加到结果的第i+j 位上。这里i, j 都是从右往左,从0 开始数。
ans[i+j] = a[i]*b[j];
另外注意进位时要处理,当前的值加上进位的值再看本位数字是否又有进位;前导清零。
下面是C语言的两个正大数相乘的参考代码:
1 #include<stdio.h> 2 #include<string.h> 3 4 #define MAX 1000 // 大数的最大位数 5 6 7 /* 8 大数乘法 9 参数: 10 num1为第一个因数,用字符数组保存 11 num2为第二个因数 12 sum数组保存相乘的结果 即:num1*num2=sum 13 返回值:返回数组sum的有效长度,即计算结果的位数 14 */ 15 int Multiplication(char num1[],char num2[], int sum[]) 16 { 17 int i, j, len, len1, len2; 18 int a[MAX+10] = {0}; 19 int b[MAX+10] = {0}; 20 int c[MAX*2+10] = {0}; 21 22 len1 = strlen(num1); 23 for(j = 0, i = len1-1; i >= 0; i--) //把数字字符转换为整型数 24 a[j++] = num1[i]-'0'; 25 len2 = strlen(num2); 26 for(j = 0, i = len2-1; i >= 0; i--) 27 b[j++] = num2[i]-'0'; 28 29 for(i = 0; i < len2; i++)//用第二个数乘以第一个数,每次一位 30 { 31 for(j = 0; j < len1; j++) 32 { 33 c[i+j] += b[i] * a[j]; //先乘起来,后面统一进位 34 } 35 } 36 37 for(i=0; i<MAX*2; i++) //循环统一处理进位问题 38 { 39 if(c[i]>=10) 40 { 41 c[i+1]+=c[i]/10; 42 c[i]%=10; 43 } 44 } 45 46 for(i = MAX*2; c[i]==0 && i>=0; i--); //跳过高位的0 47 len = i+1; // 记录结果的长度 48 for(; i>=0; i--) 49 sum[i]=c[i]; 50 return len; 51 } 52 53 int main() 54 { 55 int i, len; 56 int sum[MAX*2+10] = {0}; // 存放计算的结果,低位在前,高位在后,即sum[0]是低位 57 char num1[] = "123456789123456789"; // 第一个大数 58 char num2[] = "123456789123456789"; // 第二个大数 59 len = Multiplication(num1, num2, sum); 60 // 输出结果 61 printf("%s * %s = ", num1, num2); 62 for(i = len-1; i>=0; i--) 63 printf("%d", sum[i]); 64 printf(" "); 65 return 0; 66 }
4、大数除法
大数除法是四则运算里面最难的一种。不同于一般的模拟,除法操作不是模仿手工除法,而是利用减法操作来实现的。其基本思想是反复做除法,看从被除数里面最多能减去多少个除数,商就是多少。逐个减显然太慢,要判断一次最多能减少多少个整数(除数)的10的n次方。
以7546除以23为例:
先用7546减去23的100倍,即减去2300,可以减3次,余下646,此时商就是300 (300=100*3);
然后646减去23的10倍,即减去230,可以减2次,余下186,此时商就是320 (320=300+10*2);
然后186减去23,可以减8次,余下2,此时商就是328 (328=320+1*8);
因为2除以23的结果小于1,而我们又不用计算小数点位,所以不必再继续算下去了。
下面是C语言的两个正大数相除的参考代码,计算结果中没有小数:
1 #include<stdio.h> 2 #include<string.h> 3 #define MAX 1000 // 大数的最大位数 4 5 // 注: 6 // 本代码在以下博客代码中进行修改: 7 // http://www.cnblogs.com/javawebsoa/archive/2013/08/01/3231078.html 8 // 9 10 11 /* 12 函数SubStract功能: 13 用长度为len1的大整数p1减去长度为len2的大整数p2 14 结果存在p1中,返回值代表结果的长度 15 不够减:返回-1 , 正好够:返回0 16 */ 17 int SubStract(int *p1, int len1, int *p2, int len2) 18 { 19 int i; 20 if(len1 < len2) 21 return -1; 22 if(len1 == len2 ) 23 { // 判断p1 > p2 24 for(i = len1-1; i >= 0; i--) 25 { 26 if(p1[i] > p2[i]) // 若大,则满足条件,可做减法 27 break; 28 else if(p1[i] < p2[i]) // 否则返回-1 29 return -1; 30 } 31 } 32 for(i = 0; i <= len1-1; i++) // 从低位开始做减法 33 { 34 p1[i] -= p2[i]; // 相减 35 if(p1[i] < 0) // 若是否需要借位 36 { // 借位 37 p1[i] += 10; 38 p1[i+1]--; 39 } 40 } 41 for(i = len1-1; i >= 0; i--) // 查找结果的最高位 42 { 43 if( p1[i] ) //最高位第一个不为0 44 return (i+1); //得到位数并返回 45 } 46 return 0; //两数相等的时候返回0 47 } 48 49 50 /* 51 大数除法---结果不包括小数点 52 num1 被除数 53 num2 除数 54 sum 商,存放计算的结果,即:num1/num2=sum 55 返回数组sum的有效长度,即商的位数 56 */ 57 int Division(char num1[], char num2[], char sum[]) 58 { 59 int k, i, j; 60 int len1, len2, len=0; //大数位数 61 int dValue; //两大数相差位数 62 int nTemp; //Subtract函数返回值 63 int num_a[MAX] = {0}; //被除数 64 int num_b[MAX] = {0}; //除数 65 int num_c[MAX] = {0}; //商 66 67 len1 = strlen(num1); //获得大数的位数 68 len2 = strlen(num2); 69 70 //将数字字符转换成整型数,且翻转保存在整型数组中 71 for( j = 0, i = len1-1; i >= 0; j++, i-- ) 72 num_a[j] = num1[i] - '0'; 73 for( j = 0, i = len2-1; i >= 0; j++, i-- ) 74 num_b[j] = num2[i] - '0'; 75 76 if( len1 < len2 ) //如果被除数小于除数,直接返回-1,表示结果为0 77 { 78 return -1; 79 } 80 dValue = len1 - len2; //相差位数 81 for (i = len1-1; i >= 0; i--) //将除数扩大,使得除数和被除数位数相等 82 { 83 if (i >= dValue) 84 num_b[i] = num_b[i-dValue]; 85 else //低位置0 86 num_b[i] = 0; 87 } 88 len2 = len1; 89 for(j = 0; j <= dValue; j++ ) //重复调用,同时记录减成功的次数,即为商 90 { 91 while((nTemp = SubStract(num_a, len1, num_b+j, len2-j)) >= 0) 92 { 93 len1 = nTemp; //结果长度 94 num_c[dValue-j]++; //每成功减一次,将商的相应位加1 95 } 96 } 97 // 计算商的位数,并将商放在sum字符数组中 98 for(i = MAX-1; num_c[i] == 0 && i >= 0; i-- ); //跳过高位0,获取商的位数 99 if(i >= 0) 100 len = i + 1; // 保存位数 101 for(j = 0; i >= 0; i--, j++) // 将结果复制到sum数组中 102 sum[j] = num_c[i] + '0'; 103 sum[j] = '