【转】https://www.cnblogs.com/yueansixing/articles/9093475.html
什么是高精度数?
在一般的科学计算中,会经常算到小数点后几百位或者更多,当然也可能是几千亿几百亿的大数字。一般这类数字我们统称为高精度数,高精度算法是用计算机
对于超大数据的一种模拟加,减,乘,除,乘方,阶乘,开放等运算。
对于一个很大的数字N >= 10^ 100, 很显然这样的数字无法在计算机中正常存储。于是, 我们想到了办法,将这个数字拆开,拆成一位一位的 或者是四位四位
的存储到一个数组中, 用一个数组去表示一个数字。这样这个数字就被称谓是高精度数。
对于高精度数,也要像平常数一样做加减乘除以及乘方的运算,于是就有了高精度算法:
由于计算机输入计算结果的精度通常受到计算机的限制,如:在双精度方式下,计算机最多只能输出16位有效数字,如果超过16位,则只能按浮点形式输出,
另外,一般计算机实数表示的范围为1038,如果超过这个范围,计算机就无法表示了。但是我们可以通过一些简单的办法来解决这个问题。这就是我们要说
的高精度计算机。
一、高精度数的输入和存储方法
单个高精度数字可能长达200位,可以通过两种方式读入:1. 单字符读取、判断并存储,直到遇到“\n”(换行符),可以使用scanf("%c", &c)
,或c=getchar()
这种方法麻烦;2. 将数字作为字符串读入,存入字符数组中,这种方法最简单、高效:scanf("%s", temp);
字符到数字:使用字符数组存储容易,但计算比较麻烦,因数字在字符数组中是以字符0~9的形式存在(ASCII码范围:48~57),将所有数位以数字0~9的形式转存在一个int数组中会方便运算。N==‘N‘-‘0‘
可以实现字符N到数字N的转换。
反序存放:使用竖式做加法运算时,规则是最低位和最低位对齐,在int数组中如果将数字正序存放,对应计算位的下标就不一样,不便于计算,因此我们将数字反序存放在数组中,就可以使得低位和低位一一对应(下标相同),方便运算,如12345+9987:
//array index 1 2 3 4 5 6 7 8 9
5 4 3 2 1
+ 7 8 9 9
-------------
2 3 3 2 2
//jw 1 1 1 1
//array index是数组下标,jw代表进位
存储位数: 上面的例子并没有使用数组的0号单元,这主要是为了方便运算的缘故,例如n位数字,我们从1循环至n即可,这样思路更加清晰。而留下的0号单元刚好可以用来存储数组的长度,长度Array[0]=strlen(temp);
void getNum(int num[]){ //数组初始化和高精度数字构建
char temp[500];
scanf("%s", temp); //数字作为字符串读入
memset(num, 0, sizeof(int)*500);//将数组的所有字节置0
num[0]=strlen(temp); //将位数存于num[0]中
for(int i=1; i<=num[0]; i++) //逆序将每一位存储于num中
num[i]=temp[num[0]-i]-‘0‘;
return;
}
//使用方法
int main(){
int num1[500];
int num2[500];
getNum(num1);
getNum(num2);
......
return 0;
}
上面的代码中用到了
memset()
,该函数位于cstring头文件中,用于内存块的赋值,在例子中起到了初始化数组的作用
参数1是内存块的起始地址, num是数组的名字,即起始地址
参数2是初始化值,范围是0~255,因memset是按字节为单位进行的
参数3是初始化的字节数,sizeof(int)计算出int的字节数,乘以数组大小500
这里若是没有用
memset
进行内存块初始化会导致运算出错:如果参与计算的数字A和数字B位数不同,高位相加时,小数字的高位空间的值具有不确定性,如31415+12:
//array index 1 2 3 4 5 6 7 8 9 10 ...
A 5 1 4 1 3
B 2 1 ? ? ?
------------------------
C 7 2 ? ? ?
//jw 0 0 ? ? ?
可见,数组在使用前全部置0可以避免这个问题,你也可以用循环来解决,当然使用memset更加方便。
二、高精度加法
按位相加:从两个数A、B的低位开始按位相加,一边加一边进位,C[i]=A[i]+B[i]+jw
,进位是上一次相加产生的。
void jiafa(int A[], int B[], int C[]){ //C=A+B
int i, jw; //i从1循环至max(A[0], B[0])
for(i=1, jw=0; i<=A[0] || i<=B[0]; i++){
C[i]=A[i]+B[i]+jw; //按位相加
jw=C[i]/10; //求进位
C[i]%=10; //与10求余
}
C[i]=jw; //存放最后一个进位
C[0]=C[i] ? i : i-1; //确定C的位数
return;
}
//使用方法
int main(){
int num1[500];
int num2[500];
int result[500];
getNum(num1);
getNum(num2);
memset(result, 0, sizeof(result)); //在当前函数中才可以如此计算大小
jiafa(num1, num2, result);
printNum(result); //后面提供函数代码
return 0;
}
三、高精度减法
大小判断:相减之前要判断两个数A、B的大小关系,如果A>B,则计算A-B,否则计算B-A,需要编程实现大小判断。
按位相减:从两个数A、B的低位开始按位相减,如果L[i]<R[i]
,就需要向高位借位,因为我们已经确定了左数字L大于右数字R,一定能借到。 if(L[i]<R[i]){ C[i]=L[i]+10-R[i]; L[i+1]--;}
确定有效位:按位减完后,高位有可能变为0,如1000-990后等于10,这时就要重新计算确定结果的有效位数C[0]
是多少。可以从位置max(L[0], R[0]
开始倒推,直至遇到非0值停止,这时的位置就是有效位数。
bool compare(int L[], int R[]){ //实现两个高精度数字的大小比较
if(L[0]>R[0]) return true; //位数高的大
if(L[0]<R[0]) return false; //位数低的小
int i;
for(i=L[0]; i>=1; i--){ //位数相同逐位比较
if(L[i]>R[i]) return true; //数字大的大
if(L[i]<R[i]) return false; //数字小的小
}
return true; //能执行到这里说明相等,返回true
}
void jianfa(int L[], int R[], int C[]){ //C=L-R
for(int i=1; i<=L[0]; i++)
C[i]=L[i]; //把L的所有数位复制到C
C[0]=L[0]; //C的位数暂等于L的位数
int i;
for(i=1; i<=R[0]; i++){ //右值有多少位,就减多少次
if(C[i]<R[i]){
C[i+1]--; //向高位借1
C[i]+=10; //当前位+10
}
C[i]-=B[i]; //按位减
}
if(i<C[0]) return; //计算有效位数:L[0]-R[0]>=2
else{ //计算有效位数:L[0]-R[0]==1 or 0
while(C[i]==0 && i>=1) i--;
A[0]=(i==0) ? 1 : i; //例如10000-9999
}
return;
}
//使用方法
int main(){
int num1[500];
int num2[500];
int result[500];
getNum(num1);
getNum(num2);
memset(result, 0, sizeof(result));
if(compare(num1, num2)) jianfa(num1, num2, result);
else jianfa(num2, num1, result);
printNum(result);
return 0;
}
四、高精度乘法
类似加法,可以用竖式求乘法,同样也有进位,对每一位进行乘法运算时,必须进行错位相加:如856×25,见如下规律:
//array index 1 2 3 4 5 6 7 8 9 ...
A 6 5 8
B 5 2
----------------------------------
0 8 2 4
2 1 7 1
----------------------------------
0 0 4 1 2
//array index 1 2 3 4 5 6 7 8 9 ...
A a1 a2 a3 a4
B b1 b2
----------------------------------
C c1 c2 c3 c4
c2 c3 c4 c5
//本例中未写出进位
综上分析,乘法要进行B[0]
轮,每轮要进行A[0]
次乘法和进位运算,这就找到了循环的规律。结合错位相加,可知C[i+j-1]=C[i+j-1]+A[i]*B[i]+jw
,进位jw=c[i+j-1]/10
有效位数:计算完成后,需要确定结果的有效位数,使用和减法类似的方法从可能的最大位数往后推,直到遇到非0的数位,当前位置就是有效位数。对于M位的数字A和N位的数字B向乘,最大位数为M+N
。
void chengfa(int A[], int B[], int C[]){ //C=A*B
int i,j;
for(i=1; i<=B[0]; i++){ //B[0]轮乘法
int jw; //进位
for(j=1, jw=0; j<=A[0]; j++){ //每轮按位乘A[0]次
C[i+j-1]=C[i+j-1]+A[j]*B[i]+jw; //错位相加
jw=C[i+j-1]/10; //求进位
C[i+j-1]%=10; //进位后,与10求余
}
C[i+A[0]]=jw; //存储最后一个进位
}
int len=A[0]+B[0]; //最大可能位数
while(C[len]==0 && len>1) len--; //确定有效位数
C[0]=len;
return;
}
//使用方法
int main(){
...
chengfa(num1, num2, result);
printNum(result);
return 0;
}
五、高精度除低精度
高精度初低精度采用竖式思想,由于除数是低精度,最后的商可能是高精度,余数一定为低精度。
商有效位数:M位的数字A除以N位的数字B,商的位数最大为M-N+1
,因为B是低精度数字,计算长度N比较麻烦,简单起见,也可以认为商的最大位数为M
, 从最大数位
往后推,直到遇到非0的数位,当前位置就是有效位数。
以下是一个模拟计算过程的例子,113056/23:
//
被除数数位 当前位数字 过程被除数 过程除结果
i=6 1 1/23 商:0
i=5 1 11/23 商:0
i=4 3 113/23 商:4,余:21
i=3 0 210/23 商:9,余:3
i=2 5 35/23 商:1,余:12
i=1 6 126/23 商:5,余:11
//计算结果 商:4915, 余数:11
代码示例:
void chuDi(int A[], int B, int C[], int &yushu){ //A/B=C 余数:yushu int i, t; //t为过程被除数 for(i=A[0], t=0; i>=1; i--){ //从被除数高位循环到低位 t=t*10+A[i]; //更新t C[i]=t/B; //计算C[i] t%=B; //更新t } yushu=t; //计算完后t就是余数 i=A[0]; //计算C有效位数 while(C[i]==0 && i>1) i--; C[0]=i; return; } //使用方法 int main(){ int num1[500]; int num2; int shang[500]; int yushu; getNum(num1); scanf("%d", num2); chuDi(num1, num2, shang, yushu); printNum(shang); count<<" "<<yushu; return 0; }
六、高精度除高精度
高精度除高精度是这几种运算中最难的一种。仍然可以模仿竖式的方式进行,过程有点儿麻烦。另一种办法是利用减法:被除数A-除数B,看看能减多少个,直到A<B
,这时剪去的个数就是商,剩下的A就是余数。如果利用前面编写好的高精度减法jianfa()
和compare()
的话,实现起来岂不是很容易?可惜不是这样,假设被除数A的位数M与除数B位数N之差M-N
很大,这个范围有可能从0~200,按照最大的情况200考虑,这意味着商值也是高精度数字,而你要减10^200次才能减完,这是一个天文数字。
所以,明白否?解决这个问题也很简单,我们不一个一个减,而是按照最大可能的数量级减,例如:12345/45:
//商的最大位数i=M-N+1,即4,设计一个 临时减数,减数后面补齐i-1个0,再进行减法
i=4 12345 < 45000 可以减0个 shang[4]=0 减后A:12345
i=3 12345 < 4500 可以减2个 shang[3]=2 减后A:3345
i=2 3345 < 450 可以减7个 shang[2]=7 减后A:195
i=1 195 < 45 可以减4个 shang[1]=4 减后A:15
//因shang[4]=0,故商的有效位数shang[0]--,为3
//结果 商为274,余数15
看明白了吗,这样的计算过程,仅仅减了2+7+4=13
次,而非274
次,可见一个一个减是绝对不靠谱的。下面提供了代码,注意:减法函数jianfa()
被修改了,使得直接把结果存入A中,而不需要存在另一个数组C中,这是为了简化后面运算的缘故。
void jianfa(int A[], int B[]){ //修改了的减法,使得直接在A数组上减
int i; //不再需要存在数组C,简化后面的运算
for(i=1; i<=B[0]; i++){
if(A[i]<B[i]){
A[i+1]--; //向高位借1
A[i]+=10; //当前位+10
}
A[i]-=B[i]; //按位减
}
if(i<A[0]) return; //计算有效位数
else{
while(A[i]==0 && i>=1) i--;
A[0]=(i==0)? 1 : i;
}
return;
}
//shang=A/B, yushu为余数
bool chuGao(int A[], int B[], int shang[], int yushu[]){
memset(shang, 0, sizeof(int)*300); //初始化shang
memset(yushu, 0, sizeof(int)*300); //初始化yushu
shang[0]=A[0]-B[0]+1; //商的最大数位
for(int i=shang[0]; i>=1; i--){ //从高位到低位开始计算商
int jianshu[300]; //构造要减的减数
memset(jianshu, 0, sizeof(jianshu));
//这个函数下面有解释
memcpy(jianshu+i, B+1, sizeof(int)*B[0]);
jianshu[0]=B[0]+i-1;