对于一道A+B的题目,如果A和B在int型范围内,这个程序会很容易写出,但是A和B是几百位甚至几千位的整数呢?不管是int型还是长整型都没有办法进行运算。这时就只能模拟四则运算的过程。在本篇博客中,博主主要就自己的认识给大家分享一下关于大整数的操作,可能会因为能力问题而解释不到位,希望读者多多包涵。
大整数的存储
先来看一看大整数的存储。为了方便获取大整数的长度,一般会定义一个结构体,用于存放大整数及它的长度,此外还需要初始化结构体,即用一个构造函数。在这里我们用一个int型数组来存放大整数:d[1000],即一个1000位以内的大整数。
struct bign{
int d[1000];
int len;
bign(){
memset(d,0,sizeof(d));
len=0;
}
};
在输入大整数时,一般都是用字符串读入的吧,但是结构体里的数组是int型的,因此我们不妨将该字符串转换成整型然后存入bign结构体中。本篇博客中规定整数的高位存储在数组的高位,整数的低位存储在数组的地位。 由于输入str时,整数的高位会变成数组的地位,而整数的低位会变成数组的高位,因此为了让整数在bign中是顺序位,需要让字符串倒着存入数组中。
bign change(string str){
bign a;
a.len=str.length();
for(int i=0;i<a.len;i++)
a.d[i]=str[a.len-1-i]-'0';
return a;
}
再来看看如何比较两个大整数的大小:我们可以先判断他们的长度,长度长的那一个数肯定大,长度小的那一个数肯定小。如果相等,则从数组低位进行比较(数组低位即大整数的高位),直至1出现一个不等的位。对于两个大整数a,b,我们规定如果a大则返回1,a小则返回-1,相等则返回0,于是有:
int compare(bign a,bign b){
if(a.len>b.len) return 1; //a大
else if(b.len>a.len) return -1; //b大
else{
for(int i=a.len-1;i>=0;i--){
if(a.d[i]>b.d[i]) return 1; //a大
else if(b.d[i]>a.d[i]) return -1; //b大
}
return 0; //a和b相等
}
}
做好以上这些准备后,下面可以正式切入主题了,先来看一看大整数的四则运算吧。
大整数的四则运算
1、高精度加法
以456+78为例子,先令进位carry为0。
1、8+6+carry=14,取个位数4作为该位的结果,取十位数1作为进位。
2、5+7+carry=13,取个位数3作为该位的结果,取十位数1作为进位。
3、4+0+carry=5,取个位数5作为该位的结果,不进位,因为carry=0。
因此答案为534。此时可以把以上步骤归纳为:对其中一位进行加法时,将该位上的两个数字相加,相加后的个位数作为该位的结果,十位数作为进位,由此可以得出高精度加法运算的代码:
bign add(bign a,bign b){
bign c;
int carry=0; //进位
for(int i=0;i<a.len||i<b.len;i++){
int temp=a.d[i]+b.d[i]+carry;
c.d[c.len++]=temp%10; //取个位作为该位的结果
carry=temp/=10; //取十位作为新的进位
}
if(carry!=0) //最后可能存在进位
c.d[c.len++]=carry;
return c;
}
需要注意的是,该代码有一个前提条件,即a和b都是正整数。如果有一个是负数,可以采用高精度的减法;如果两个都是负数,则先取他们的绝对值进行运算,最后在将运算结果加上负号即可。
2、高精度的减法
还是以456-78为例。
1、6-8<0,此时不够减,需要向高位借位,即5减1等于4,该位结果为16-8=8。
2、4-7<0,此时不够减,需要向高位借位,即4减1等于3,该位结果为14-7=7。
3、3-0=3。
因此答案为378。现在对于以上步骤我们进行归纳:对某一步,比较被减位和减位,看他们够不够减,如果不够减,则向高位借位,高位减1,被减位加10,此时再进行计算;如果够减,则直接减去。将减去的结果作为该位的结果。最后一步还要注意的是,高位可能有多余的0,要去除他们,但是也要保证结果至少有一位数。由此可以得出减法运算的代码:
bign sub(bign a,bign b){
bign c;
for(int i=0;i<a.len||i<b.len;i++){
if(a.d[i]<b.d[i]){
//不够减则向高位借位
a.d[i+1]--;
a.d[i]+=10;
}
c.d[c.len++]=a.d[i]-b.d[i];
}
while(c.len-1>=1&&c.d[c.len-1]==0) //消除高位多余的0
c.len--;
return c;
}
需要注意的数,对于a和b来说,被减数一定要大于减数,因此在调用该函数时,先比较a和b的大小,若a小则进行交换,并且输出负号,再使用该函数。
3、高精度与低精度的乘法
高精度与低精度的乘法就是bign型与int型的乘法。
以147×35为例,先定义一个进位carry并令其为0。
1、7×35=245,245+carry=245,取个位数5作为该位的结果,高位部分24作为进位。
2、4×35=140,140+carry=164,取个位数4作为该位的结果,高位部分16作为进位。
3、1×35=35,35+carry=51,取个位数1作为该位的结果,高位部分5作为进位。
4、乘完了,如果进位不为0,就把进位直接作为结果的高位。
因此答案为5145,对每一步进行归纳:取bign的某位与int型整数进行相乘,再与进位相加,所得结果的个位作为该位的结果,高位部分作为新的进位。所以有:
bign multi(bign a,int b){
bign c;
int carry=0; //定义进位
for(int i=0;i<a.len;i++){
int temp=a.d[i]*b+carry;
c.d[len++]=temp%10; //取个位作为结果
carry=temp/10; //取高位作为新的进位
}
while(carry!=0){
//乘法的进位可能不止一位
c.d[len++]=carry%10;
carry/=10;
}
return c;
}
如果a和b中存在一个负数,则先记录负号,再取绝对值相乘。如果两个都是负数,则取他们的绝对值进行相乘。
5、高精度与低精度的除法
以1234/7为例,先定义一个余数r为0。
1、(1+r*10)与7进行比较,不够除,因此该位商为0,余数为1。
2、(2+r*10)与7进行比较,够除,因此该位商为1,余数为5。
3、(3+r*10)与7进行比较,够除,因此该位商为7,余数为4。
4、(4+r*10)与7进行比较,够除,因此该位商为6,余数为2。
对上述步骤进行归纳:上一步的余数乘以10(十进制)加上该步的位,得到的结果与除数进行比较,如果不够除,该位商为0;如果够除,则商即为对应的商,余数为对应的余数。最后一步还要去除高位多余的0,并且保证结果至少有一位数。于是有:
bign divide(bign a,int b,int &r){
bign c;
c.len=a.len; //先令其长度相等
r=0; //令余数r为0
for(int i=a.len-1;i>=0;i++){
//从高位开始除
r=r*10+a.d[i]; //和上一步遗留的余数组合
if(r<b) c.[i]=0; //不够除
else{
//够除
c.d[i]=r/b; //商作为该位的结果
r%=b; //新的余数
}
}
while(c.len-1>=1&&c.d[c.len-1]==0) //去除高位的0
c.len--;
return c;
}
大整数的进制转换
将大整数a的m进制转换成n进制,我们可以先调用稍加改动的divide()函数,把单次计算取余的结果存入string数组的ans中。
获取余数:
bign SysConvert(bign a,int m,int n,int &r){
bign c;
c.len=a.len;
r=0;
for(int i=a.len-1;i>=0;i--){
r=r*m+a.d[i]; //注意此时是将余数乘m,因为是m进制
if(r<n) c.d[i]=0;
else{
c.d[i]=r/n;
r%=n;
}
}
while(c.len-1>=1&&c.d[c.len-1]==0)
c.len--;
return c;
}
存储余数:
string ans;
int r;
bign a=change(str);
do{
a=SysConvert(a,m,n,r);
ans.push_back(r+'0');
}while(!(a.d[0]==0&&a.len==1));
此时ans中的逆序就是n进制的a。