最近在洛谷上写题的时候,看到一个算1000!的题目,但是C语言和C++算到13的阶乘,就存放不下了,所以就根据答案,写了一个算大数阶乘的程序,与此同时,受到启发,就又写了写大数的加法减法和乘法,对于除法,我现在唯一想到的办法是进行多次减法,所以等以后有了好的想法再补上!
对于C /C++,它们的数据类型有限,int最大为2^31 -1, 大约二十亿,long long最多是2^63-1,十进制也就二十位左右。
但是,如果一个数超过了20位,甚至一百位,两百位,那么这些基本的数据类型就不足以进行计算了。因此,我们就需要用到高精度的运算,说白了就是用数组来存放大数,然后模拟列竖式的过程就行。接下来依次说明
1.按正常字符串的方式读入数字,读入之后都减去’0’变成数字
2.运算和进位分开做,先按位进行运算,之后统一处理进位,这样不会搞混
3.运算的时候从最后一位开始,把结果写进另一个数组里,这里指的是正序写入另一个数组,这就需要在输出结果时逆序输出了
No Picture You Say a …
加法比较容易,直接按位相加就行。加法函数里,为了避免分类讨论两个加数位数的问题,所以把两个加数都加到了同一个数组里。这是最重要的部分
这里主要是来写一下整体的代码,后面乘法和减法里相似的部分就不再说明了
#include
#include
void toNum(char s[]);//转换位数字
int add(char a[], int len1, char b[], int len2, int result[]);//按位相加
int carry(int result[], int len);//进位
int main()
{
char a[400];
char b[400];
int result[410]={0};
//buffer
scanf("%s", a);
scanf("%s", b);
int len1 = strlen(a);
int len2 = strlen(b);
toNum(a);
toNum(b);
int len3 = add(a,len1, b, len2, result);
int len4 = carry(result, len3);
int i=0;
for (i=len4-1; i>=0; i--)
{
printf("%d", result[i]);
}
printf("\n");
return 0;
}
void toNum(char s[])
{
int i;
int len = strlen(s);
for (i=0; i<len; i++)
{
s[i] -= '0';
}
}
int add(char a[], int len1, char b[], int len2, int result[])
{
int i=len1-1, j=len2-1, k1=0, k2=0;
while (i>=0)
{
result[k1] += a[i];
i--;
k1 ++;
}
//先把第一个数加到result数组里
while (j>=0)
{
result[k2] += b[j];
j--;
k2 ++;
}
//再把第二个数也加到result数组里
return (k1>k2?k1:k2);//返回大的位数,实际操作了多少位
}
int carry(int result[], int len)
{
int i=0;
for (i=0; i<len; i++)
{
result[i+1] += result[i]/10;//一定要先进位再取余
result[i] %= 10;
}
if (result[len]==0)//如果最高位是0,就说明没进位
return len;
else
return len+1;
}
接下来的代码都是由三个部分构成,转换为数字(toNum),运算(add,sub,multi)
,进位(carry)等,主要说明的是运算函数
减法和加法大同小异,首先是按位相减,然后是进位变成了借位,借位就是如果这一位小于0,就让前一位-1,这一位加10
No Picture You Say a…
大致思路就是这样,下面是代码
int sub(char a[], int len1, char b[], int len2, int result[])
{
int i=len1-1, j=len2-1, k1=0, k2=0;
while (i>=0)
{
result[k1] += a[i];//也不是非得用+=,直接赋值就行
i--;
k1 ++;
}
//把第一个数赋值给result,这样才能减
while (j>=0)
{
result[k2] -= b[j];
j--;
k2 ++;
}
return k1;
}
int carry(int result[], int len)//这是借位的处理
{
int i=0;
for (i=0; i<len; i++)
{
if (result[i]<0)//小于0时才能说明借位了
{ //然后加10减1就行
result[i] += 10;
result[i+1]--;
}
}
while(result[len-1]==0&&len>1)//把前导0都去了,才能得到位数
{
len--;
}
return len;
}
这里需要说明的就是:减法函数里默认了第一个数是大于第二个数的,这样也就更简单了,实际上,为了达到这个效果,前面可以再加一个缓冲区,专门用来调换顺序,控制正负号。减法就说到这里
乘法最重要的是乘法函数的设计,与加减法不同,乘法需要一个数每位都与另一个数相乘再相加,加的时候要对齐,比如第二位进行相乘,就从第二位开始往后加。其实,这样也还没解释清楚,先从低精度乘高精度说吧
低精度就是说int能表示的数,比如9,100这些,相乘的时候让这些数和高精度的每一位都相乘,按位记录下来,乘完之后再考虑进位,
No Picture You Say a…
这和实际的程序还是有点出入的,主要是顺序不一样,这个图为了说明实际的原理也就没考虑那么多的细节了,应该很清楚了吧
接下来解释高精度乘以高精度
这是对前面的一个扩展,需要把每一位都当作低精度来和另一个数相乘,然后再把每一位的积相加,这里关键在于相加时候的对齐问题,考虑到每次前进一位就相当于乘以了10,所以在相加的时候也要先乘10,反映到数组里就是往前走一位对齐。也就是说,这是第几位,就要把积从第几位开始对齐相加
No Picture You Say a …
后面就是求和,化的图比较丑,就不再画了(逃)
终于到了代码时间,上代码
int multi(char a[], int len1, char b[], int len2, int result[])
{
if (a[0]==0||b[0]==0)
{
return 1;
}//如果输入两个0的话,就不需要乘了
int i, j, k=0;
for (i=len1-1; i>=0; i--)
{
k = len1-1-i;//这是负责对齐的
for (j=len2-1; j>=0; j--)
{
result[k] += a[i]*b[j];
k++;
}
}
return k;
}
int carry(int num[], int len)
{
int i;
for (i=0; i<len; i++)
{
num[i+1] += num[i]/10;
num[i] %= 10;
}
while (num[i])
{
num[i+1] += num[i]/10;
num[i] %= 10;
i++;
}//这是为了把每个单元都变成只有一个数字,方便后续使用
return i;
}
至于除法,还没有想到高级的模拟方法,等以后想到了再补充上。
最后,这些其实都不算完美,比如没有对正负号进行判断,只是用于整数等,这些不足都还有待改进,总之,不断地学习新知识,不断总结积累,才能有更完善的思路,更优质的解题过程。这次的总结,就先到这里吧!