目录
一.产生原因
二.不同方法介绍
三.模拟过程(核心代码)
1.加法(进位)
2.减法(借位)
3.乘法(高精度a*低精度b)
4.除法(高精度a÷低精度b)
四.完整代码
1.加法
2.减法
3.乘法
4.除法
首先明确为什么有高精度运算即大数的运算。由于C++中最大的long long也只能存到2^63-1=9223372036854775807(19位数),但当数字长度接近10^5甚至更长时就没办法用long long储存了,所以不能直接对变量进行加减,因此有了高精度算法,最近我刚学,怕忘就总结了一下,仅供参考,如有错误还请指正。
对了:题目指路
加法:https://www.acwing.com/problem/content/793/
减法:https://www.acwing.com/problem/content/794/
乘法:https://www.acwing.com/problem/content/795/
除法:https://www.acwing.com/problem/content/796/
高精度算法一般有两种形式,①数组模拟 ②用STL中的vector容器 。
①:将字符串string中的每一位转化为int数组中的数;
②:用vector中
的函数push_back(); pop_back(); back(); front(); begin(); 进行操作;
两者的模拟加法的大致思想是一样的,但STL容器可以在空间上随用随申请,而数组只能提前申请(a[100010])固定空间。
这个是平时咱们计算的过程(红1表示进位)而写程序时咱们需要找到一个循环起来的方法,于是继续思考我们为什么会知道进位以及进多少
上图为直接对每一位相加之后的结果,从最低位(个位)看起,就可以发现8=18%10,9=(18+1)%10,1=(0+1)%10。这样就找到规律了:
每一位上的数字=(进位+加数上同位两数字的和)%10
进位=(进位+加数上同位两数字的和)/10
注:这里是默认的a的长度>b的长度!
(数组模拟,rr[]为结果)
for(int i=0; i<len; i++)//len为aa,bb两个数字的长度的最大的一个 { rr[i]=aa[i]+bb[i]; } for(int i=0; i<len; i++) { if(rr[i]>=10) rr[i+1]++,rr[i]-=10; } if(rr[len]) len++;//这里处理a+b后最高位的进位问题
注:这里一定要分两个for来加和判断,要不然循环第二次进行时会覆盖++,进位就没有用到!
等等,写到这里我突然意识到只要把res那改成+=就可以了!妙蛙!
for(int i=0; i<len; i++) { rr[i]+=aa[i]+bb[i]; if(rr[i]>=10) rr[i+1]++,rr[i]-=10; }
(vector)
for(int i=0; i<a.size()+1; i++)//这里size+1是防止长度增加的进位 { t+=a[i]; if(i<b.size()) t+=b[i]; //这里分成两部分加是为了防止vector越界,数组不用是因为设置全局数组后有默认值0 res.push_back(t%10); t/=10; }
从结果上来看a-b可能是正数可能是负数,负数时加上符号变成b-a即可,这里不再赘述。
比较简单的情况是被减数的每一位都恰好比减数的每一位大,这里就不列举了;
比较复杂的就是下图这样有借位的情况:
还是和加法一样先对每一位做减法(红字),然后从低位(个位)开始考虑借位变成下图(蓝字)
就可以找到规律:
每一位上的数字=同位减法,判断是否<0,小于则下一位(高位)--,本位+=10
Or:
每位数字=(同位减法+10-标记)%10,如果减法<0则标记1,否则标记0
(这个标记是在下一位(高位)时才开始起作用,模拟借位的过程,先判断有没有被借过位)
注:这里默认a>b!
(数组)
for(int i=0; i<lena; i++) rr[i]=aa[i]-bb[i]; for(int i=0; i<lena; i++) if(rr[i]<0) rr[i]+=10,rr[i+1]--;
有了加法的启示这里也可以改良一下
for(int i=0; i<lena; i++) { rr[i]+=aa[i]-bb[i]; if(rr[i]<0) rr[i]+=10,rr[i+1]--; }
(vector)
for(int i=0; i<a.size(); i++) { t=a[i]-t; if(i<b.size()) t-=b[i];//和加法同理 res.push_back((t+10)%10); if(t<0) t=1; else t=0; }
数组模拟法的难点在于一个公式的理解:
res[i+j]=a[i]*b[j]
这里我想了一个神奇的例子,希望可以帮助理解:
结果中res[7]=6,乘数中a[2]=2,b[5]=3,从这里我们可以看出res[2+5]=a[2]*b[5],规律立现!
容器法就相对简单一点,直接暴力每一位都乘一遍b,取模作为这一位上的结果,其实和加法类似。
每一位数字=(a[i]*b+jw)%10;
进位+=(a[i]*b+jw)/10;
(数组)
for(int i=0; i<lena; i++) { int jw=0;//进位 for(int j=0; j<lenb; j++) { rr[i+j]+=aa[i]*bb[j]+jw; jw=rr[i+j]/10; rr[i+j]%=10; } rr[i+lenb]=jw;//两位数乘一位数得到三位数的情况 }
(vector)
vec mul(vec &a,int b) { vec res; int t=0; for(int i=0; i<a.size() || t; i++) { if(i<a.size()) t+=a[i]*b; res.push_back(t%10); t/=10; } }
首先1/31=0,
(1*10+2)/31=0,((1*10+2)*10+0)/31=3 ,得到余数27,模拟完过程就可以找到规律了!
每位数字=(上一位的余数*10+本位)/除数
余数=(上一位的余数*10+本位)-除数*结果位数字
由于除法的力量过于玄学,此处就只放vector版本了。
r=0;//余数要有初始值0 for(int i=a.size()-1; i>=0; i--)//从高位除起,所以后续不需要reverse { r=r*10+a[i]; res.push_back(r/b); r%=b; }
篇幅有限,我就不介绍一些细节比如去除前导0了,我会在代码中注释一下。
#include <iostream> #include <cstring> #define N 10010 using namespace std; char a[N],b[N],res[N]; int aa[N],bb[N],rr[N]; int lena,lenb; int main() { scanf("%s",a); scanf("%s",b); int lena=strlen(a),lenb=strlen(b); int len=max(lena,lenb); for(int i=0; i<lena; i++) aa[i]=a[lena-i-1]-'0';//先逆序存到int数组里方便处理 for(int i=0; i<lenb; i++) bb[i]=b[lenb-i-1]-'0'; //为啥要逆序呢,为了让长度不等的两个独立对象右对齐 for(int i=0; i<len; i++) { rr[i]+=aa[i]+bb[i]; if(rr[i]>=10) rr[i+1]++,rr[i]-=10; } if(rr[len]) len++;//多进一位的情况,比如99+1=100 for(int i=len-1; i>=0; i--) printf("%d",rr[i]); }
#include <iostream> #include <cstring> #define N 10010 using namespace std; char a[N],b[N]; int aa[N],bb[N],rr[N]; int lena,lenb; bool flag=true; int cmp()//a>b->1 a<b->-1 a==b->0 { lena=strlen(a); lenb=strlen(b); if(lena>lenb) return 1; else if(lena<lenb) return -1; else return strcmp(a,b); } int main() { while(scanf("%s%s",a,b)!=2)//万一只有一个输入值 { cout<<a; return 0; } if(cmp()==0) { cout<<0; return 0; } else if(cmp()<0) swap(a,b),swap(lena,lenb), cout<<"-";//b-a->a-b for(int i=0; i<lena; i++) aa[i]=a[lena-i-1]-'0'; for(int i=0; i<lenb; i++) bb[i]=b[lenb-i-1]-'0'; for(int i=0; i<lena; i++) { rr[i]+=aa[i]-bb[i]; if(rr[i]<0) rr[i]+=10,rr[i+1]--; } int i=lena-1; while(!rr[i]) i--;//去除前导0 for(; i>=0; i--) printf("%d",rr[i]); }
#include <iostream> #include <vector> #include <algorithm> using namespace std; typedef vector<int> vec; vec mul(vec &a,int b) { vec res; int t=0; for(int i=0; i<a.size() || t; i++) { if(i<a.size()) t+=a[i]*b;//用if防止i越界,vector与数组不一样在于不会默认补0 res.push_back(t%10); t/=10; } while(res.size()>1 && res.back()==0) res.pop_back();//去除前导0(现在还在0最后面) reverse(res.begin(),res.end()); return res; } int main() { string a; int b; vec aa,rr; cin>>a>>b; for(int i=a.size()-1; i>=0; i--) aa.push_back(a[i]-'0'); rr=mul(aa,b); for(int i=0; i<rr.size(); i++) printf("%d",rr[i]); }
#include <iostream> #include <vector> #include <algorithm> using namespace std; typedef vector<int> vec; int r; vec div(vec &a,int b) { vec res; r=0; for(int i=a.size()-1; i>=0; i--)//从高位除起,所以后续不需要reverse { r=r*10+a[i]; res.push_back(r/b); r%=b; } while(res.size()>1 && res.front()==0) res.erase(res.begin());//delete 0 return res; } int main() { string a; int b; vec aa,rr; cin>>a>>b; for(int i=a.size()-1; i>=0; i--) aa.push_back(a[i]-'0'); rr=div(aa,b); for(int i=0; i<rr.size(); i++) printf("%d",rr[i]); cout<<endl<<r; }