简介
对于A+B,如果A和B是有着1000个数位的整数,就不可以用基本的数据类型如int来存储了。这时只可以老实地模拟加减乘除的过程。大整数又称为高精度整数,其含义就是用基本数据类型无法存储其精度的整数。
大整数的存储
我们可以用数组来存储大整数,比如用int型数组d[1000],这个数组的每一位就代表存放整数的每一位。数组的高下标存储的是整数的高位,低下标存储的是整数的低位。
为了方便获取大整数的长度,一般定义一个int型变量len来记录长度,并与d数组构成结构体。
struct bign
{
int d[1000];
int len;
};
最好在定义结构体之后马上对其进行初始化,防止后续忘记。可以在结构体中直接设置构造函数。
struct bign
{
int d[1000];
int len;
bign()
{
memset(d,0,sizeof(d));
len=0;
}
};
在输入大整数的时候,一般都是先用字符串读入,然后再把字符串另存至结构体。注意,要倒着存储。
bign change(char str[])
{
bign a;
a.len=strlen(str);
for(int i=0;i<a.len;i++)
{
a.d[i]=str[a.len-i-1]-'0';
// a.d[i]=str[a.len-i-1]-48;
}
return a;
}
比较2个大整数的大小时,先比较2者的len,如果不相等,谁大谁的值就大。如果相等,从高位一次比较。
int compare(bign a,bign b)
{
if(a.len>b.len)
return 1;
else if(a.len<b.len)
return -1;
else
{
for(int i=a.len-1;i>=0;i--)
{
if(a.d[i]>b.d[i]) return 1;
else if(a.d[i]<b.d[i]) return -1;
}
return 0; //两数相等
}
}
大整数的四则运算
(1)高精度加法(2)高精度减法 (3)高精度与低精度的乘法(4)高精度与低精度的除法
一般不会考虑高精度与高精度的乘除法。
高精度加法
回忆小学时期的加法
从低位开始。
7+5=12 保留个位数2,向高位进1
4+6=10,再加上进位的1为11,保留个位数1,向高位进1
1+0=1,再加上进位的1,为2。
因此每一位进行加法的步骤:将该位上2个数字和进位相加,取个位数作为该位结果,取十位数作为新的进位。
bign add(bign a,bign b) //高精度A+B
{
bign c;
int carry=0 //进位,初始位0;
for(int i=0;i<a.len||i<b.len;i++) //以较长的为界限
{
int temp=carry+a.d[i]+b.d[i]; //对应位与进位相加
c.d[c.len++]=temp%10;
carry=temp/10;
}
if(carry!=0) //进位不为0,直接赋给结果的最高位。
{
c.d[c.len++]=carry;
}
return c;
}
#include <iostream>
#include <cstdio>
#include <cstring>
struct bign
{
int d[1000];
int len;
bign()
{
memset(d,0,sizeof(d));
len=0;
}
};
bign change(char str[])
{
bign a;
a.len=strlen(str);
for(int i=0;i<a.len;i++)
{
a.d[i]=str[a.len-i-1]-'0';
// a.d[i]=str[a.len-i-1]-48;
}
return a;
}
bign add(bign a,bign b) //高精度A+B
{
bign c;
int carry=0; //进位,初始位0;
for(int i=0;i<a.len||i<b.len;i++) //以较长的为界限
{
int temp=carry+a.d[i]+b.d[i]; //对应位与进位相加
c.d[c.len++]=temp%10;
carry=temp/10;
}
if(carry!=0) //进位不为0,直接赋给结果的最高位。
{
c.d[c.len++]=carry;
}
return c;
}
void print(bign a)
{
for(int i=a.len-1;i>=0;i--)
{
printf("%d",a.d[i]);
}
}
int main()
{
char s1[100],s2[100];
scanf("%s%s",s1,s2);
bign a=change(s1);
bign b=change(s2);
print(add(a,b));
return 0;
}
这样的写法是2个非负整数 如果一方是负的,可以再转换到数组时去掉负号,采用高精度减法,如果都是负的,采用高精度加法,最后结果取负。
高精度减法
回忆小学时期的减法
依旧从低位开始
5-7 失败,从高位借1,4变为3,10+5-7=8
3-6失败,从高位借1,1变为0,10+3-6=7
0-0=0 (要特别注意:减法后高位可能存在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;
}
高精度乘法
高精度乘法这里指bign类型与int类型的乘法,其与之前的乘法运算有点不同。以147*35为例,这里把147视为bign类型数据,35视为int类型。在求积中始终把35看作整体。
7*35=245 取个位数5作为该位结果,高位24作为进位
4*35=140,加上进位24,164 把个位数4作为该位结果,高位16作为进位。
1*35=35,加上进位16,得51,把个位数1作为该位结果,高位5作为进位。
没有数继续乘了,就把进位5直接作为结果的高位。(注意在没数可乘时,如果进位大于10,需要循环进位)
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[c.len++]=temp%10;
carry=temp/10;
}
while(carry!=0)
{
c.d[c.len++]=carry%10;
carry/=10;
}
return c;
}
高精度与高精度乘法
首先说一下乘法计算的算法,从低位向高位乘,在竖式计算中,我们是将乘数第一位与被乘数的每一位相乘,记录结果,之后,用第二位相乘,记录结果并且左移一位,以此类推,直到计算完最后一位,再将各项结果相加,得出最后结果。
计算的过程基本上和小学生列竖式做乘法相同。为了编程方便,并不急于处理进位,而是将进位问题留待最后统一处理。
总结一个规律: 即一个数的第i 位和另一个数的第j 位相乘所得的数,一定是要累加到结果的第i+j 位上。这里i, j 都是从右往左,从0 开始数。
ans[i+j] = a[i]*b[j];
另外注意进位时要处理,当前的值加上进位的值再看本位数字是否又有进位;前导清零。
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;
int size_plus;
void multi(string &s1,string &s2,int c[])
{
int n=s1.size();
int m=s2.size();
int a[n];
int b[m];
int i,j;
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
for(j=0,i=n-1;i>=0;i--)
{
a[j++]=s1[i]-'0';
}
for(j=0,i=m-1;i>=0;i--)
{
b[j++]=s2[i]-'0';
}
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
{
c[i+j]+=a[j]*b[i];
}
}
for(i=0;i<size_plus-1;i++)
{
c[i+1]+=c[i]/10;
c[i]=c[i]%10;
}
}
int main()
{
string s1,s2;
cin>>s1>>s2;
int size1=s1.size();
int size2=s2.size();
size_plus=size1+size2+5;
int res[size_plus];
memset(res,0,sizeof(res));
multi(s1,s2,res);
int i;
for(i=size_plus-1;i>=0;i--)
{
if(res[i]!=0)
break;
}
int len=i+1;
for(int j=len-1;j>=0;j--)
{
printf("%d",res[j]);
}
return 0;
}
高精度与低精度的除法
除法运算与小学时的运算大致相同
1与7比较,不够除,商为0,余数为1
余数1和新位2组成12,12与7比较,够除,商为1,余数为5
余数5与新位3组合为53,53与7比较,够除,商为7,余数为4
余数4与新位4组合为44,44与7比较,够除,商为6,余数为2;
可以看出:上一步的余数乘以10加上该步的位,得到该步的临时被除数,将其与除数比较。若不够除,则商为0.要注意减法后高位可能会有多余的0,要忽视他们,但也要保证至少有一位数。
bign division(bign a,int b,int &r) //有的题目野火要求球的余数。
{
bign c;
c.len=a.len;
for(int i=a.len-1;i>=0;i--) //从高位开始
{
r=r*10+a.d[i];
if(r<b)
c.d[i]=0;
else
{
c.d[i]=r/b;
r=r%b; //新的余数
}
}
while(c.len-1>=1&&c.d[c.len-1]==0)
{
c.len--;
}
return c;
}
整体测试
#include <iostream>
#include <cstdio>
#include <cstring>
struct bign
{
int d[1000];
int len;
bign()
{
memset(d,0,sizeof(d));
len=0;
}
};
bign change_to_bign(char str[])
{
bign a;
a.len=strlen(str);
for(int i=0;i<a.len;i++)
{
a.d[i]=str[a.len-i-1]-'0';
// a.d[i]=str[a.len-i-1]-48;
}
return a;
}
int change_to_int(char str[])
{
int ans=0;
int len=strlen(str);
int temp;
for(int i=0;i<len;i++)
{
temp=str[i]-'0';
// a.d[i]=str[a.len-i-1]-48;
ans=ans*10+temp;
}
return ans;
}
bign add(bign a,bign b) //高精度A+B
{
bign c;
int carry=0; //进位,初始位0;
for(int i=0;i<a.len||i<b.len;i++) //以较长的为界限
{
int temp=carry+a.d[i]+b.d[i]; //对应位与进位相加
c.d[c.len++]=temp%10;
carry=temp/10;
}
if(carry!=0) //进位不为0,直接赋给结果的最高位。
{
c.d[c.len++]=carry;
}
return c;
}
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)
{
c.len--;
}
return c;
}
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[c.len++]=temp%10;
carry=temp/10;
}
while(carry!=0)
{
c.d[c.len++]=carry%10;
carry/=10;
}
return c;
}
bign division(bign a,int b,int &r) //有的题目野火要求球的余数。
{
bign c;
c.len=a.len;
for(int i=a.len-1;i>=0;i--) //从高位开始
{
r=r*10+a.d[i];
if(r<b)
c.d[i]=0;
else
{
c.d[i]=r/b;
r=r%b; //新的余数
}
}
while(c.len-1>=1&&c.d[c.len-1]==0)
{
c.len--;
}
return c;
}
void print(bign a)
{
for(int i=a.len-1;i>=0;i--)
{
printf("%d",a.d[i]);
}
}
int main()
{
char s1[100],s2[100];
//模拟加法
printf("请输入要进行加法操作的2个数\n");
scanf("%s%s",s1,s2);
bign a=change_to_bign(s1);
bign b=change_to_bign(s2);
printf("%s+%s=",s1,s2);
print(add(a,b));
printf("\n");
//模拟减法
printf("请输入要进行减法操作的2个数\n");
scanf("%s%s",s1,s2);
a=change_to_bign(s1);
b=change_to_bign(s2);
printf("%s-%s=",s1,s2);
print(sub(a,b));
printf("\n");
//模拟乘法
printf("请输入要进行乘法操作的2个数\n");
scanf("%s%s",s1,s2);
bign f=change_to_bign(s1);
int d=change_to_int(s2);
printf("%s*%s=",s1,s2);
print(multi(f,d));
printf("\n");
//模拟除法
printf("请输入要进行除法操作的2个数\n");
int r=0;
scanf("%s%s",s1,s2);
a=change_to_bign(s1);
int e=change_to_int(s2);
printf("%s/%s=",s1,s2);
print(division(a,e,r));
printf(" %s%%%s=%d",s1,s2,r);
printf("\n");
}
题目一 幂运算
给定两个数R和n,输出R的n次方,其中0.0<R<99.999, 0<n<=25
#include <cstdio>
#include <cstring>
struct bign
{
int d[127];
int len;
bign()
{
memset(d,0,sizeof(d));
len=0;
}
};
bign change_str_to_bign(char str[],int *length)
{
bign c;
int len_=strlen(str);
for(int i=len_-1;i>=0;i--)
{
if(str[i]=='.')
{
*length=len_-1-i;
}
else
c.d[c.len++]=str[i]-'0';
}
return c;
}
int change_str_to_int(char str[])
{
int c=0;
int len_=strlen(str);
for(int i=0;i<len_;i++)
{
if(str[i]!='.')
{
c=c*10+str[i]-'0';
}
}
return c;
}
bign multi(bign a,int b)
{
bign result;
int carry=0;
for(int i=0;i<a.len;i++)
{
int temp=a.d[i]*b+carry;
result.d[result.len++]=temp%10;
carry=temp/10;
}
while(carry)
{
result.d[result.len++]=carry%10;
carry/=10;
}
return result;
}
int main()
{
char str[7];
int r;
while(scanf("%s%d",str,&r))
{
int length=0;
bign result;
result.d[0]=1;
result.len=1;
bign a=change_str_to_bign(str,&length);
int b=change_str_to_int(str);
int number_of_minus=length*r;
for(int i=0;i<r;i++)
{
result=multi(result,b);
}
//如果总位数小于小数位数,说明是纯小数
if(result.len<=number_of_minus)
{
printf("0.");
for(int i=0;i<number_of_minus-result.len;i++)
printf("0");
for(int i=result.len-1;i>=0;i--)
{
printf("%d",result.d[i]);
}
}
//不是纯小数
else
{
int j=0;
while(result.d[j]==0&&j<=number_of_minus-1)
{
j++;
}
for(int i=result.len-1;i>=j;i--)
{
if(i+1==number_of_minus)
printf(".");
printf("%d",result.d[i]);
}
}
}
return 0;
}
但是放到题目里边运行,总是提示我运行时间超时!可能是结构体很占用时间?
用字符串或数组表示大数,否则无法精确表示答案1的情况。同时注意2数相乘的位数。
函数BigDataMulti用于计算当前计算值(从数组尾到数组头为数字)与底数的乘积
#include <cstdio>
#include <cstring>
int len;
void BigDataMulti(int a[],int data)
{
int i;
int carry=0;
for(i=0;i<len;i++)
{
int temp=a[i]*data+carry;
a[i]=temp%10;
carry=temp/10;
}
while(carry)
{
a[i++]=carry%10;
carry/=10;
}
len=i;
}
int main()
{
int n; //指数
char s[7]; //底数,最多为6位。
int result[126]={0}; //存放结果,做多位5*25+1位;
while(scanf("%s %d",s,&n)!=EOF)
{
int num=0; //字符数组转化位整数的值
int pos=0;
int position=0; //结果中小数位数
for(int i=0;i<strlen(s);i++)
{
if(s[i]=='.')
pos=strlen(s)-i-1;
else
num=num*10+s[i]-'0';
}
position=pos*n;
result[0]=1; //初始化结果为1,其中的结果是倒叙的,比如数组内容是12345时,其实结果为54321;
len=1; //结果长度初始化为1
for(int i=0;i<n;i++)
{
BigDataMulti(result,num);
}
//输出
if(len<=position) //全为小数
{
printf("0.");
for(int i=0;i<position-len;i++)
{
printf("0");
}
for(int i=len-1;i>=0;i--)
{
printf("%d",result[i]);
}
}
else
{
int j=0;
while(result[j]==0&&j<=position-1) //去掉尾部的0,但也要防止非小数部分的0也被去掉。很重要!
{
j++;
}
for(int i=len-1;i>=j;i--)
{
if(i+1==position)
printf(".");
printf("%d",result[i]);
}
}
}
return 0;
}