什么是高精度数?
在一般的科学计算中,会经常算到小数点后几百位或者更多,当然也可能是几千亿几百亿的大数字。一般这类数字我们统称为高精度数,高精度算法是用计算机对于超大数据的一种模拟加,减,乘,除,乘方,阶乘,开放等运算。
对于一个很大的数字N >= 10^ 100, 很显然这样的数字无法在计算机中正常存储。于是, 我们想到了办法,将这个数字拆开,拆成一位一位的 或者是四位四位的存储到一个数组中, 用一个数组去表示一个数字。这样这个数字就被称谓是高精度数。
对于高精度数,也要像平常数一样做加减乘除以及乘方的运算,于是就有了高精度算法:
由于计算机输入计算结果的精度通常受到计算机的限制,如:在双精度方式下,计算机最多只能输出16位有效数字,如果超过16位,则只能按浮点形式输出,另外,一般计算机实数表示的范围为1038,如果超过这个范围,计算机就无法表示了。但是我们可以通过一些简单的办法来解决这个问题。这就是我们要说的高精度计算机。
一、基本方法:
(一)、数据的接收与存储
要在计算机上进行高精度计算,首先就应该有精确的输入,即计算机要精确地接收和存储数据。
基本思路:
1、以字符串的方式读入数字并保存在ac字符数中。
2、用strlen函数计算字符数组ac中总字符数(高精度数的位数)并保存在整型数组的a[0]中。
3、将字符串中的字符逐个字符转化成数字并保存在整型数组a中。这一部分的代码编写非常重要,运算都会从低位开始 (先算个位,再算十位,再……)在存储时需要倒序存储,也就是个位数存在a[1],十位数存在a[2]最高位存在a[a[0]]。
例如数字4678在数组中存储结构为:
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
4 8 7 6 4
下面的程序dh子函数完成了以字符方式读入两个字符串并将其转化成数字存储在数组a[]和b[]中。
#include<iostream>
#include<cstring>
using namespace std;
int a[1010],b[1010]; //用于存储拆解后的数字
void dh()
{
int an,bn,i;
char ac[1010],bc[1010];
cin>>ac>>bc;
a[0]=strlen(ac); //a高精度数的位数存在a[0]中
for(i=1;i<=a[0];i++)a[i]=ac[a[0]-i]-'0'; //倒序存储数字(+、-*)
b[0]=strlen(bc); //b高精度数的位数存在b[0]中
for(i=1;i<=b[0];i++)b[i]=bc[b[0]-i]-'0';//倒序存储数字(+、-*)
return;
}
int main()
{
dh();
return 0;
}
(二)、运算结果的输出
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
4 0 0 0 1 0 0 0 0 0
b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9]
3 9 9 9 0 0 0 0 0 0
结果 1 0 0 0 0 0 0 0 0
这里只考虑高精加、高精减、高精乘这类倒序存放的数据的计算结果的输出,这三类数据由于是倒序存放,而且运算结果的位数很难确定,如1000-999=1,如下表所示
所以此类运算结果的输出一般都需要一个去除前导0的过程,也就是说忽略掉数组中后面所有的无效0,定位到数组中最右一个非0数字,再从最右侧的非0位开始依次输出数字。
具体代码:
i=1000(偷懒点的可以直接设数据位数上限)
while(a[i]==0)i--; //去除前导0
while(i>0)cout<<a[i--];
(三)、高精度数的加法运算(两个非负整数)
基本思路:
1、先以前面介绍的方式以读字符串的方式读取两个加数并转化存储到整型数组a和b中。
2、用一个变量c存储运算结果的最大位数(两个加数中的大数字的位数+1,+1是考虑到最后结果最高位位有可能有进位的问题,如50+990=1040)
3、用一个变量k来存储有可能的进位情况,初始化为0。
4、从最低位到最高位依次相加,将相加结果的个位保存在当前位,进位情况记录到k(0无进位,1 进位1),以便在进行下一次运算的时候把进位加进去。
具体代码如下:
void gjjia()
{
int i,c,k=0,t;
c=a[0]>b[0]?a[0]+1:b[0]+1;
for(i=1;i<=c;i++)
{
t=a[i]+b[i]+k;
a[i]=t%10;
k=t/10;
}
return;
}
(四)、高精度减法运算(两个数都是非负整数且被减数一定比减数大)
基本思路:
1、先以前面介绍的方式以读字符串的方式读取两个加数并转化存储到整型数组a和b中。
2、用一个变量c存储运算结果的最大位数(因为默认被减数比减数大,所的最大位数就是被减数的位数,减法运算结果的最高位不可能产生进位,不需要+1)
3、用一个变量k来存储有可能的借位情况,初始化为0。
4、从最低位到最高位依次相加,将相加结果的个位保存在当前位,进位情况记录到k(当前位的被减数比减数小需要向高位借位k=-1;否则k=0),以便在进行下一次运算的时候把借位减掉。
具体代码如下:
void gjjian()
{
int i,c,k=0,t;
c=a[0];
for(i=1;i<=c;i++)
{
if(a[i]+k>=b[i]){a[i]=a[i]+k-b[i];k=0;}
else{a[i]=a[i]+10+k-b[i];k=-1;}
}
}
(五)、高精度减法运算(两个数都是非负整数但不确定两者大小)
基本思路:由于不确定被减数和减数哪个更大,需要定义一个函数bijiao()来判断两个高精度数大小,用一个全局布尔型变量fu存储大小情况。被减数小,fu=1,否则fu=0。后面的高精减函数则根据fu的值来确定减法策略,fu=0时b-a否则是a-b,输出函数则根据fu情况来确定是否输出负号。
判断两数大小函数:
bool bijiao()
{
int ai,bi;
ai=a[0];
while(a[ai]==0)a[ai--];//为了让大小比较更准确,都去掉前导0
bi=b[0];
while(b[bi]==0)b[bi--];//为了让大小比较更准确,都去掉前导0
if(ai<bi)return 0; //如果被减数的位数比减数小,返回0
else if (ai>bi)retrun 1; //如果被减数的位数比减数大,返回1
else //两者位数相等则从高位开始逐位判断。
{
for(int j=ai;j>0;j--)
if(a[j]<b[j])return 0;
else if(a[j])>b[j])return 1;
return 1; //两个数完全相等
}
}
两数相减函数
void gjjian()
{
int i,c,k=0,t;
if(fu==1)
{
c=a[0];
for(i=1;i<=c;i++)
{
if(a[i]+k>=b[i]){a[i]=a[i]+k-b[i];k=0;}
else {a[i]=a[i]+10+k-b[i];k=-1;}
}
}
else
{
c=b[0];
for(i=1;i<=c;i++)
{
if(b[i]+k>=a[i]){a[i]=b[i]+k-a[i];k=0;}
else{a[i]=b[i]+10+k-a[i]; k=-1;}
}
}
}
输出函数
void shuchu()
{
int i=1000;
if(fu==0)cout<<"-";
while(a[i]==0)i--;
while(i>0)cout<<a[i--];
cout<<endl;
return;
}
(六)、考虑有正负号的高精度加、减法运算。
只需要在接收数据时判断接收的数字是否是负数,再在运算中设计相应的运算策略即可,例如:a(+)+b(-)转化成a-b等,不再详细说明。
(七)、高精乘法运算。
完成高精乘法运算的话只需列一个竖式的乘法运算就能轻松完成程序设计。找到其中的关键规律:个位和个位相乘,结果保存在个位,个位和十位相乘,结果保存在十位,十位和十位相乘结果保存在百位,依次类推可以得出递推公式,第i位的数和第j位数相除,结果保存在第(i+j-1)位。
所以程序只需再定一个数组c来保存a和b各位相乘的结果,最后统一完成各位的进位就可以了,相乘部分函数代码如下
void gjchen()
{
int i,j;
for(i=1;i<=a[0];i++)
for(j=1;j<=b[0];j++)
{
c[j+i-1] = c[j+i-1] + a[i]*b[j];
c[i+j]=c[i+j]+c[i+j-1]/10;
c[i+j-1]=c[i+j-1]%10;
}
}
(八)、高精度除法。
除法运算和前面所讲的高精加,高精减以及高精除不一样,高精除是从高位开始除(不理解的话,请麻烦列一个竖式除法运算)。所有高精除的数据存储要正序存放,最高位在最左边(切记!切记!)。
同样,想要理解高精除法运算的原理,请默默的列一个竖式除法运算。
先看看高精度数除以低精度数。
设参与运算的两个数分别为a和b,以字符串的方式读入数a,并将a中的每一位数字分别存储在a数组中。由于b是低精度数,可以用一般的整数变量来接收和存储。
运算过程:由于除数是低精,所以我们完全可以定义一个整型变量t来完成除法运算,同时定义一个整型数组来存储商。第一轮算是将被除数的最高位a[1]赋值给t,将t/b的商存储在c[1](c[1]=t/b),并将余数存储在t(t=t%b)。第2轮运算就是将上轮运算的余数乘10再加上本轮补除数位数上的数字t=t*10+a[2],将t/b的商存储在c[2](c[2]=t/b),并将余数存储在t(t=t%b)。第i轮运算就是将上轮运算的余数乘10再加上本轮补除数位数上的数字t=t*10+a[i],将t/b的商存储在c[i](c[i]=t/b),并将余数存储在t(t=t%b)。直到数组的最低位a[a[0]]参与运算。
例 123456 除以5
被除数 1 2 3 4 5 6
第一步 1 1除5,商0余1;
第二步 余1 12 前面的余1*10+2=12,12除5商2余2
余2 23 前面的余2*10+3=23,23除5商4余3
余3 34 前面的余3*10+4=34,34除5商6余4
余4 45 前面的余4*10+5=45,45除5商9余0
余0 6 前面的余0*10+6=6,6除5商1余1
商 0 2 4 6 9 1 最后余1
输出商时要注意两点:1、去除无效的前导0;2、按位输出到c数组的最低位(a[0]商的最低位位数和被除数的最低位的位数相同)位为止。
代码如下:
#include<iostream>
#include<cstring>
using namespace std;
int a[1010]; //被除数
int b ; //除数
int c[1010]; //商
int d; //余数
void gjchudj()
{
c[0]=a[0];
int i, t=0;
for(i=1;i<=a[0];i++){t=t*10+a[i];c[i]=t/b;t=t%b;}
d=t;
}
void shuchu()
{
int i=1;
while(c[i]==0)i++;
while(i<=c[0])cout<<c[i++];
cout<<endl<<d<<endl;
return;
}
void dh()
{
int an,bn,i,t,d;
char ac[1010];
cin>>ac>>b;
d=strlen(ac);//a高精度数的位数存在a[0]中,要注意,如果输入的
t=0; //数字有前导0的话,需要先清除前导0,代码如下:
while(ac[t]=='0')t++;
a[0]=d-t;
for(i=t;i<=d;i++)a[i-t+1]=ac[i]-'0';
}
int main()
{
dh();
gjchudj();
shuchu();
return 0;
}
高精度数除以高精度数。
采用字符串读入的方式接收和存储数据,设参与运算的两个数分别为A和B,并将相应的字符串转化为数值,将A、B中的每一位数字分别存储在a和b数组中。
运算思路:用减法代替除法运算。
下面以12345678(存在数组a中)除以11111组b中)为例模拟整个运算过程(用c数组存商)。
第一步:用数组存确定商的最大位数。c[0]=a[0]-b[0]+1;即c[0]=4;
第二步:循环减数i从c[0]开始依次递减循环
步骤1、定义一个中间数组tt用来保存,将tt的前i-1位都赋值为0,从第i位开始将b的数值赋值给tt
当i=4的时候,tt数组的状态如下表
存储位 0 1 2 3 4 5 6 7 8
a 8 8 7 6 5 4 3 2 1 当i=4的时候将b数组所有的数据右移3(i-1)格,并存储在tt数组中。
b 5 5 4 3 2 1
tt 8 0 0 0 1 1 1 1 1
该部分子函数代码如下:(p是数b所在数组,q是数组tt)
void fuzhi(int p[],int q[],int wei)
{
int i;
for(int i=1;i<wei;i++)q[i]=0;
for(i=wei;i<p[0]+wei;i++)q[i]=p[i-wei+1]
q[0]=p[0]+wei-1;
}
步骤2、比较数组a和tt的大小。比较方法,先判断两个数的位数,a的位数多则返回1,tt的位数多则返回-1;如果位数相同则从高位开始逐位判断大小,只要在某一位上出现大小不等的情况那么直接返回相应的比较结果(a[i]>b[i]返回1;a[i]<b[i]返回-1),如果所有位数都相同则返回0。
该部分子函数代码如下:(p是数a所在数组,q是数组tt)
int bijiao(int a[],int b[])
{
int i;
if(a[0]>b[0])return 1;
if(a[0]<b[0])return -1;
for(i=a[0];i>=1;i--)
{
if(a[i]>b[i])return 1;
if(a[i]<b[i])return -1;
}
return 0;
}
步骤3、根据比较的结果执行相应的运算:
如果返回值是1则说明a比tt大,则先将c[i]++,然后a减去tt,减完后去除a的前导零,并重新确定a数组的准确位数。回到前面的步骤2重新判断a和tt的大小。
如果返回的值是0则说明a已经能被b所整除,那么c[i]++;后结束除法。
如果返回的值是 -1 则说明a比tt小,那么i--;再回到步骤1继续。
反复循环直到i=0结束循环。
整个过程列表模拟如下:
存储位 0 1 2 3 4 5 6 7 8
a 8 8 7 6 5 4 3 2 1 当i=4的时候将b数组所有的数据右移3(i-1)格,并存储在tt数组中。判断a和tt的大小。
tt 8 0 0 0 1 1 1 1 1
减后结果a 8 8 7 6 4 3 2 1 0 a>tt则a-tt,(从低位开始)c[4]+1=1
新a 7 8 7 6 4 3 2 1 去除前导零重新确定a的位数。比较a和tt的大小
原tt(i=4) 8 0 0 0 1 1 1 1 1
新tt(i=3) 7 0 0 1 1 1 1 1 a<tt则i-1(=3)重新赋值tt, 比较a和tt的大小
减后结果a 7 8 7 5 3 2 1 0 a>tt则a-tt(从低位开始),c[3]+1=1
新a 6 8 7 5 3 2 1 去除前导零重新确定a的位数。比较a和tt的大小
tt(i=3) 7 0 0 1 1 1 1 1
新tt(i=2) 6 0 1 1 1 1 1 a<tt则i-1(=2)重新赋值tt, 比较a和tt的大小
减后结果a 6 8 6 4 2 1 0 a>tt则a-tt(从低位开始),c[2]+1=1
新a 5 8 6 4 2 1 去除前导零重新确定a的位数。比较a和tt的大小
tt(i=2) 6 0 1 1 1 1 1
新tt(i=1) 6 1 1 1 1 1 a<tt则i-1(=1)重新赋值tt, 比较a和tt的大小
减后结果a 5 7 5 3 1 0 a>tt则a-tt(从低位开始),c[2]+1=1
新a 4 7 5 3 1 去除前导零重新确定a的位数。比较a和tt的大小
tt(i=1) 6 1 1 1 1 1 a<tt则i-1(=0)结束除法。
最后c 4 1 1 1 1 去除前导零,输出商1111
最后a 4 7 5 3 1 去除前导零,输出余数1357
整个高精除高精的除法完整代码如下:
#include <bits/stdc++.h>
using namespace std;
int a[101],b[101],c[101],d,i;
void shuru(int a[])
{
char s[101];cin>>s; //读入字符串
a[0]=strlen(s); //a[0]储存字符串的长度
for (i=1;i<=a[0];i++)
a[i]=s[a[0]-i]-'0'; //将字符串转化为数组a,并倒序储存
}
void shuchu(int a[])// 用于输出最后的答案,并注意若答案为0的情况
{
int i;
if (a[0]==0) {cout<<"0"<<endl;return;}
for (i=a[0];i>0;i--) cout<<a[i];
cout<<endl;
return;
}
int bijiao(int a[],int b[])//比较a和b的大小关系,若a>b则为1,若a<b则为-1,若a=b则为0
{
int i;
if (a[0]>b[0]) return 1; //若a的位数大于b,则a>b
if (a[0]<b[0]) return -1; //若a的位数小于b,则a<b
for (i=a[0];i>0;i--)//从高位到低位依次比较,找出大小关系
{
if (a[i]>b[i]) return 1;
if (a[i]<b[i]) return -1;
}
return 0;
}
void jian(int a[],int b[]) //a数组既做被除数,又作为储存余数
{
int pd,i;
pd=bijiao(a,b); //调用函数比较ab大小
if (pd==0) {a[0]=0;return;} //相等
if (pd==1)
{
for (i=1;i<=a[0];i++)
{
if (a[i]<b[i]) {a[i+1]--;a[i]+=10;} //若不够减上借一位
if (a[i]>=b[i]) a[i]-=b[i];
}
while((a[a[0]]==0)&&(a[0]>0)) a[0]--;
return;
}
}
void numcpy(int p[],int q[],int det) //复制p到q从wei开始的地方
{
for (int i=1;i<=p[0];i++) q[i+det-1]=p[i];//将数组右移,使两个数组右端对齐,形参q数组储存右移后的结果
q[0]=p[0]+det-1;
}
void chugao(int a[],int b[],int c[])
{
int i,tmp[101];
c[0]=a[0]-b[0]+1;
for (i=c[0];i>0;i--)
{
memset(tmp,0,sizeof(tmp)); //tmp数组清零
numcpy(b,tmp,i); //将除数b右移后复制给tmp数组,注意用i控制除数位数
while (bijiao(a,tmp)>=0){c[i]++;jian(a,tmp);} //减法模拟除法,并计数
}
while((c[c[0]]==0)&&(c[0]>0)) c[0]--; // 控制最高位的0
}
int main()//主程序
{
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
shuru(a);shuru(b);
chugao(a,b,c);
shuchu(c);
shuchu(a);
return 0;
}