高精度
简介
众所周知,在计算机中,每个数据类型都是有存储上限的,那么当数字特别大时应该怎么办呢?这时高精度就产生了。高精度的主要思想就是模拟手算,然后将结果存储到数组中去,相同的,小数也有精度问题,也可以使用相同的思路
存储
这里使用vector
来进行存储,因为这样不需要去管结果有多少位,直接使用push_back()
函数就行了,虽然和数组比起来会慢一些,不过差别也仅仅是常数而已
输入:定义一个字符串,然后将字符串的每位转数字存储起来就行了
string a; vector <int> A;
cin>>a;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
输出:请注意,输入的时候我是反过来的,这样做是为了添加元素比较方便,那么输出的时候也要注意倒着输出
for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
高精度加法
上文中已经提到,在进行高精度运算是模拟手算的,那么接下来就来回忆一下,我们是怎么手动做加法的
图为用竖式做加法的示例,我们可以发现主要组成部分有两个加数、结果、还有进位,于是我们的变量就可以呼之欲出了
vector <int> A; vector <int> B; vector <int> C; int t;
然后我们再使用循环遍历(从0开始)来进行计算,循环位数较大的那个加数的每一位,然后加到 t t t 里面就行了
那么 C i C_i Ci 就等于 A i + B i A_i+B_i Ai+Bi 再 m o d 10 mod~10 mod 10,进位 t t t 就等于 ( A i + B i ) / 10 (A_i+B_i)/10 (Ai+Bi)/10
注意在循环完之后,如果 t ≠ 0 t\neq 0 t=0 的话,还要加上一位
代码模板
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
vector<int> add(vector<int>& A,vector<int>& B)
{
vector<int> C;
int t=0;
for(int i=0;i<max(A.size(),B.size());i++)
{
if(i<A.size()) t+=A[i];
if(i<B.size()) t+=B[i];
C.push_back(t%10);
t/=10;
}
if(t) C.push_back(t);
return C;
}
int main()
{
string a,b;
cin>>a;
cin>>b;
vector<int> A;
vector<int> B;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=0;i--) B.push_back(b[i]-'0');
vector<int> C=add(A,B);
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
return 0;
}
高精度减法
不带负数
首先,先回忆一下我们是怎么用竖式进行减法的
与加法不同,我们在列减法的竖式时会把大数放在上面,小数放在下面,因为减法涉及到借位的问题
所以说在计算机计算的时候,我们要先判断 A A A 是否 ≥ B \geq B ≥B,如果 < B <B <B,为了避免增加代码量我们直接计算 − ( A − B ) -(A-B) −(A−B) 就行了
那么因为数字特别大,所以我们也需要手写一个比较函数,那么我们是怎么比较两个数的呢?
先比较哪个位数大,位数多的大,如果位数一样,那么分别比较每一位
bool cmp(vector<int>& A, vector<int>& B)
{
if (A.size() != B.size())return A.size() > B.size();
for (int i = A.size()-1; i>=0; i--)
{
if (A[i] != B[i]) return A[i] > B[i];
}
return true;
}
类似加法,变量分别为被减数,减数,结果,借位
使用循环遍历(从0开始)被减数的每一位
借位 t t t = A i − ( B i ) − t A_i-(B_i)-t Ai−(Bi)−t,如果说最终结果小于0,就要借一位,将 t + 10 t+10 t+10 最后再 m o d 10 mod~10 mod 10 便是 C i C_i Ci
如果借位了 t = 1 t=1 t=1 否则 t = 0 t=0 t=0
注意:为了方便,我们直接不管结果是否小于0,每次都加10,因为如果结果不小于0的话 + 10 +10 +10 m o d 10 mod~10 mod 10 就抵消了对结果不影响
最后还要去除前导0
while (C.size() > 1 && C.back() == 0)
{
C.pop_back();
}
代码模板
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
bool cmp(vector<int>& A,vector<int>& B)
{
if(A.size()!=B.size()) return A.size()>B.size();
for(int i=A.size()-1;i>=0;i--)
{
if(A[i]!=B[i]) return A[i]>B[i];
}
return true;
}
vector<int> sub(vector<int>& A,vector<int>& B)
{
int t=0;
vector<int> C;
for(int i=0;i<A.size();i++)
{
t=A[i]-t;
if(i<B.size()) t-=B[i];
C.push_back((t+10)%10);
if(t<0) t=1;
else t=0;
}
while(C.size()>1&&C.back()==0) C.pop_back();
return C;
}
int main()
{
string a,b;
vector<int> A,B;
cin>>a;
cin>>b;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=0;i--) B.push_back(b[i]-'0');
if(cmp(A,B))
{
vector<int> C=sub(A,B);
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
else
{
vector<int> C=sub(B,A);
cout<<'-';
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
return 0;
}
带有负数
- 自然数 A A A 减负数 B B B 等于 $A+\vert B\vert $
- 负数 A A A 减自然数 B B B 等于 − ( ∣ A ∣ + B ) -(\vert A \vert+B) −(∣A∣+B)
- 自然数 A A A 减负数 B B B 等于 ∣ A ∣ + B \vert A\vert+B ∣A∣+B
- 负数 A A A 减负数 B B B 当 ∣ A ∣ ≤ ∣ B ∣ \vert A\vert\leq\vert B\vert ∣A∣≤∣B∣ 时,等于 ∣ B ∣ − ∣ A ∣ \vert B\vert -\vert A\vert ∣B∣−∣A∣ 当 ∣ A ∣ > ∣ B ∣ \vert A \vert>\vert B\vert ∣A∣>∣B∣ 时,等于 − ( ∣ A ∣ − ∣ B ∣ ) -(\vert A\vert-\vert B\vert) −(∣A∣−∣B∣)
代码模板
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
bool cmp(vector<int>& A,vector<int>& B)
{
if(A.size()!=B.size()) return A.size()>B.size();
for(int i=A.size()-1;i>=0;i--)
{
if(A[i]!=B[i]) return A[i]>B[i];
}
return true;
}
bool cmp1(vector<int>& A,vector<int>& B)
{
if(A.size()!=B.size()) return A.size()>B.size();
for(int i=A.size()-1;i>=0;i--)
{
if(A[i]!=B[i]) return A[i]>B[i];
}
return false;
}
vector<int> add(vector<int>A,vector<int>B)
{
vector<int> C;
int t=0;
for(int i=0;i<max(A.size(),B.size());i++)
{
if(i<A.size()) t+=A[i];
if(i<B.size()) t+=B[i];
C.push_back(t%10);
t/=10;
}
if(t) C.push_back(t);
return C;
}
vector<int> sub(vector<int>& A,vector<int>& B)
{
int t=0;
vector<int> C;
for(int i=0;i<A.size();i++)
{
t=A[i]-t;
if(i<B.size()) t-=B[i];
C.push_back((t+10)%10);
if(t<0) t=1;
else t=0;
}
while(C.size()>1&&C.back()==0) C.pop_back();
return C;
}
int main()
{
string a,b;
vector<int> A,B;
cin>>a;
cin>>b;
if(a[0]!='-'&&b[0]!='-')
{
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=0;i--) B.push_back(b[i]-'0');
if(cmp(A,B))
{
vector<int> C=sub(A,B);
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
else
{
vector<int> C=sub(B,A);
cout<<'-';
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
}
else if(a[0]=='-'&&b[0]!='-')
{
for(int i=a.size()-1;i>=1;i--) A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=0;i--) B.push_back(b[i]-'0');
vector<int> C=add(A,B);
cout<<'-';
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
else if(a[0]!='-'&&b[0]=='-')
{
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=1;i--) B.push_back(b[i]-'0');
vector<int> C=add(A,B);
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
else if(a[0]=='-'&&b[0]=='-')
{
for(int i=a.size()-1;i>=1;i--) A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=1;i--) B.push_back(b[i]-'0');
if(cmp1(A,B))
{
cout<<'-';
vector<int> C=sub(A,B);
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
else
{
vector<int> C=sub(B,A);
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
}
return 0;
}
高精度乘法
高精度乘低精度
高精度乘法与前面有所不同,在我们用竖式计算乘法时,都是一位对一位的,而在这里因为
b
b
b 比较小,所以我们直接拿大数的每一位去乘上
b
b
b 就行了,那么我们还是拿一个竖式举例:
先模拟一下这个例子:
C
0
=
(
3
×
12
)
m
o
d
10
=
6
C_0=(3×12)~mod~10=6
C0=(3×12) mod 10=6
t
1
=
3
×
12
/
10
=
3
t_1=3×12/10=3
t1=3×12/10=3
C
1
=
(
2
×
12
+
t
1
)
m
o
d
10
=
7
C_1=(2×12+t_1)~mod~10=7
C1=(2×12+t1) mod 10=7
t
2
=
2
×
12
/
10
=
2
t_2=2×12/10=2
t2=2×12/10=2
C
2
=
(
1
×
12
+
2
)
m
o
d
10
=
4
C_2=(1×12+2)~mod~10=4
C2=(1×12+2) mod 10=4
t
3
=
1
×
12
/
10
=
1
t_3=1×12/10=1
t3=1×12/10=1
C
3
=
t
3
=
1
C_3=t_3=1
C3=t3=1
那么最终的结果就是 1476
我们用循环遍历 A A A 的每一位就行了
如果最后 t ≠ 0 t\neq0 t=0,那么就添上 t m o d 10 t~mod~10 t mod 10,注意因为我们是直接乘上 b b b 所以进位不一定是个位数,要用一个循环来做
代码模板
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
vector<int> mul(vector<int>& A,int B)
{
int t=0;
vector<int> C;
for(int i=0;i<A.size();i++)
{
t+=A[i]*B;
C.push_back(t%10);
t/=10;
}
while(t)
{
C.push_back(t%10);
t/=10;
}
return C;
}
int main()
{
string a;
int B;
cin>>a;
cin>>B;
vector<int> A;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
vector<int> C=mul(A,B);
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
高精度乘高精度
因为两个数都很大,所以我们按照手算的方式进行计算,首先先举个例子:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7CPZw5OA-1691065706967)(E:\图片\高精度4.png)]
我们可以发现 A 0 × B 0 A_0×B_0 A0×B0 对应 C 0 C_0 C0 A 1 × B 0 A_1×B_0 A1×B0 对应 C 1 C_1 C1 A 2 × B 0 A_2×B_0 A2×B0 对应 C 2 C_2 C2 以此类推……
我们可以得出 A i × B j A_i×B_j Ai×Bj 对应 C i C_i Ci + _+ + j _j j
那么进位 t t t 就等于 C i C_i Ci + _+ + j _j j / 10 /10 /10
与大数乘小数一样,最后还要添上 t t t,不过这里是模拟手算,不需要再循环了
因为不能直接往后添数,所以我们的答案先初始化长一点,否则无法存储 vector<int> C(A.size() + B.size() + 7, 0);
最后记得去除前导0
代码模板
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
vector<int> mul(vector<int>& A,vector<int>& B)
{
vector<int> C(A.size()+B.size()+7,0);
for(int i=0;i<A.size();i++)
{
int t=0;
for(int j=0;j<B.size();j++)
{
C[i + j] += A[i] * B[j] + t;
t = C[i + j] / 10;
C[i + j] %= 10;
}
if(t) C[i+B.size()]+=t;
}
while(C.size()>1&&C.back()==0) C.pop_back();
return C;
}
int main()
{
string a,b;
cin>>a; cin>>b;
vector<int> A,B;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=0;i--) B.push_back(b[i]-'0');
vector<int> C=mul(A,B);
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
注意: t t t 在一轮用完后要及时初始化为0,另外 t t t 一定要赋值为 C i C_i Ci + _+ + j / 10 _j/10 j/10,因为当重叠时可能加法上又有进位。
高精度除法
大数除小数
首先,回想一下我们是怎么用竖式算除法的
我们可以发现几个变量分别是被除数、除数、商、余数
我们使用一个变量 r r r 来存储余数
因为计算机不像人那么聪明,所以一位一位来
首先看1,1除11不够,商0,余1
再看2,因为现在余1,所以变成10+2,12除11够,商1,余1,结束
我们就可以得到 r = r × 10 + A i m o d B r=r×10+A_i~mod~B r=r×10+Ai mod B, C i = r × 10 + A i / B C_i=r×10+A_i~/B Ci=r×10+Ai /B
注意因为我们除法是从前往后的,所以去除前导0的时候要先进行翻转
代码模板
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
vector<int> div(vector<int>& A, int& B,int& r)
{
r=0; vector<int> C;
for(int i=A.size()-1;i>=0;i--)
{
r=r*10+A[i];
C.push_back(r/B);
r%=B;
}
reverse(C.begin(),C.end());
while(C.size()>1&&C.back()==0) C.pop_back();
return C;
}
int main()
{
string a;
cin>>a;
vector<int> A; int B;
cin>>B;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
int r;
vector<int> C=div(A,B,r);
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
cout<<endl;
cout<<r;
}
压位
因为数组一个位置可以存很大的数,所以我们只存一个数有点浪费,所以我们可以使用压位这个技巧
我们先开两个全局变量 N = x N=x N=x M = 1 0 x M=10^x M=10x (x为你想压的位数)
输入的时候改成
int st=max(0,1-N+1),len=i-st+1;
A.push_back(a.substr(st,len)-'0');
注意所有
/
10
m
o
d
10
/10~mod~10
/10 mod 10 的操作均要修改成
/
M
m
o
d
M
/M~mod~M
/M mod M
输出的时候要先输出首位,因为输出的时候有可能不一定正好是
x
x
x 位数,要使用 printf("%04d",C[i])
(以4位举例)
所以当输出完首位后,从C.size()-2
开始
高精度小数
保留?位
首先,我们先算出整数部分,因为计算机不像手算,直接算出就可以了,不需要像手算一样一位一位算
然后我们回想一下手算是怎么算小数部分的,我们将上一位算得的余数乘10再除以除数就行了
示意图
代码模板
//c是位数
digit[0]=a/b;
a%=b;
for(int i=1;i<=c+1;i++)
{
a*=10;
digit[i]=a/b;
a%=b;
}
cout<<digit[0]<<'.';
for(int i=1;i<=c;i++) cout<<digit[i];
四舍五入
由于小数会出现循环,所以题目一般会给出一些要求,例如最后一位四舍五入
四舍五入即为 当数 > = 5 >=5 >=5 进一位, < 5 <5 <5 时舍去
不过我们需要注意,进位的时候可能会出现“多米诺骨牌”
例如 9.99999… 进位后会变成10.00000…,所以我们需要使用循环来处理
注意进位到整数位如果变成10不用管,因为我们是直接输出整数位的
代码模板
if(digit[c+1]>=5) digit[c]++;
for(int i=c;i>0;i--)
{
if(digit[i]>=10)
{
digit[i]-=10;
digit[i-1]++;
}
}