高精度算法是在计算问题涉及的数据范围超过该程序语言的表示能力时,用数组模拟数学运算的一类算法。本节学习高精度的整数四则运算,其中乘法只要求一个因子是高精度,除法只要求被除数是高精度。以下,用大写字母(如 A A A、 B B B)表示高精度的整数,用小写字母(如 b b b)表示用编程语言自带的整数类型就能表达的整数。
以下默认是十进制数表示下的整数四则运算操作,在其它进制表示下也是类似的。
1 高精度整数A+B
模板题:高精度加法。
要计算两个高精度整数 A A A和 B B B的和,可以用列竖式的思想,从最低位开始,将低位加起来然后进位,每次将进位 t t t都和两者尚未计算的最低位 A [ i ] A[i] A[i]和 B [ i ] B[i] B[i]加起来,其除以 10 10 10的余数就是这一位的值,商就是进位 t t t,所以:
由于 A A A和 B B B的位数不一定是一样的,这里不妨假设 A A A的长度总是 > = B >=B >=B的长度(如果输入时不满足,就交换 A A A和 B B B再做高精度加法),这样计算的时候就只要把 A A A的每一项都加完,相应的位置如果 B [ i ] B[i] B[i]还存在就加,不存在就不用加了。
在加完之后还要判断一下进位
t
t
t,如果还有进位的话,就要再在结果中加一位。只要用一个if
判断一次就可以了,不需要用while
来做,因为最后
t
t
t的值一定是
0
≤
9
<
10
0 \leq 9 < 10
0≤9<10的,因为以
A
A
A的长度,加上一个不超过其长度的
B
B
B,结果
C
C
C最大能达到的长度也就是
A
A
A的长度
+
1
+1
+1,不可能更长了。
由于是从最低位往高位一位一位加,而读取数据的时候是从高位向低位读的,所以这里将 A A A和 B B B都排成从低位向高位的,这样方便计算。相应地,得到的 C C C也是从低位到高位排的,最后记得翻转一下。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// 高精度加法,A和B都是从低位到高位存的
vector<int> add(vector<int>& A, vector<int>& B) {
// 如果A的长度不足B的长度,计算B+A,总是保证A是较长的一个
if (A.size() < B.size()) return add(B, A);
// 保存实时的进位值
int t = 0;
// 保存计算结果
vector<int> C;
// 从低位到高位遍历A(较长者)的每一位
for (int i = 0; i < A.size(); i ++ ) {
t += A[i]; // 先把A[i]加进来
if (i < B.size()) t += B[i]; // 如果B还没遍历完,把B[i]也加进来
C.push_back(t % 10); // 对10取模就是结果C的这一位的值
t /= 10; // 除以10得到的就是向高位的进位值
}
// 最后,如果进位值还不是0,说明还有一个向高位的进位,补充进去
if (t) C.push_back(t);
return C;
}
int main() {
// 由于两个数很大,用字符串读入
string a, b;
cin >> a >> 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');
// 计算高精度加法,得到的结果C也是从低位到高位存的
auto C = add(A, B);
// 从高位到低位输出才是正常阅读次序
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
cout << endl;
return 0;
}
2 高精度整数A-B
模板题:高精度减法。
要计算两个高精度整数 A A A和 B B B的差,也是用列竖式的思想。这里需要先判断一下 A A A和 B B B哪个大,如果 B B B比 A A A大,那么 A − B A-B A−B的结果是 − ( B − A ) -(B-A) −(B−A),所以只需要实现一个较大的自然数减去一个较小的自然数这个过程就可以了。
在做减法的时候,已经确定传入的 A A A是 > = B >=B >=B的了,所以 A A A的长度也是 > = B >=B >=B的,可以用和前面计算高精度加法时候类似的思想。从低位到高位遍历 A A A中的每一位,用 t t t表示来自上一位的借位(借位只能是 0 0 0或者 1 1 1) 。
那么这一位的减法实际上就是 A [ i ] − t − B [ i ] A[i] - t - B[i] A[i]−t−B[i](如果 B B B已经遍历完了,就不用减 B [ i ] B[i] B[i])。如果这个值是负的,那么就说明这一位的减法是向更高一位借了 1 1 1的,那么下一轮 t t t就要变成 1 1 1,否则下一轮 t t t的值就是 0 0 0。
向高位借 1 1 1的效果就是在本位加了一个 10 10 10,所以本位减法的计算结果可以直接用 + 10 +10 +10之后再 m o d 10 mod~10 mod 10来计算,这样不论有没有借位计算的都是正确的。
需要注意,最终的减法结果可能存在前导 0 0 0,所以需要删除高位的连续 0 0 0。然而当减法结果是 0 0 0时候,需要保留一个 0 0 0,所以在删除高位 0 0 0的时候注意一下只剩一个数的时候也要停止删除了。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// 高精度整数比较,A和B都是从低位到高位存的
// 如果A>=B返回true,否则返回false
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];
// 如果每一位都一样,说明A和B相等,也返回true
return true;
}
// 高精度减法,A和B都是从低位到高位存的
vector<int> sub(vector<int>& A, vector<int>& B) {
// 保存计算结果
vector<int> C;
// 从低位到高位遍历A的每一位
for (int i = 0, t = 0; i < A.size(); i ++ ) {
// 减去来自上一位的借位t
int val = A[i] - t;
// 如果B还没遍历完,还要减去B的这一位的数
if (i < B.size()) val -= B[i];
// 这一位的减法结果
C.push_back((val + 10) % 10);
// 判断一下是否存在向更高一位的借位,如果借了也就是借1
if (val < 0) t = 1;
else t = 0;
}
// 去除高位的连续0,注意如果计算结果是0要保留一个0,所以保持C至少有一位数就行
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main() {
// 由于两个数很大,用字符串读入
string a, b;
cin >> a >> 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');
// 计算高精度减法,存到C中
vector<int> C;
if (cmp(A, B)) C = sub(A, B);
else {
cout << '-';
C = sub(B, A);
}
// 从高位到低位输出才是正常阅读次序
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
cout << endl;
return 0;
}
3 高精度整数A*b
模板题:高精度乘法。
这里 A A A是一个高精度整数, b b b是一个普通精度的整数,计算它们的乘法也是用列竖式的方法,但是因为 b b b不是一个高精度的数,只需要从 A A A低位到高位,每次把对应位置的数字 A [ i ] A[i] A[i]乘以 b b b再加上来自低位的进位 t t t就可以了,而不需要把 b b b的每一位拆开来。
乘的结果模 10 10 10就是结果 C C C的这一位上的数,而除以 10 10 10的结果就是进位。在 A A A的每一位都乘完之后,进位的值可能还没用完,所以要重复“模 10 10 10放在下一位,除以 10 10 10作为进位”这个操作。
结果 C C C中的高位可能有连续的 0 0 0,所以还是像高精度减法中那样处理一下高位的连续 0 0 0,同时注意如果结果就是 0 0 0要留下一个 0 0 0。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// 高精度乘法,计算大整数A和普通量级的整数b的相乘结果
// A从低位到高位存储
vector<int> mul(vector<int>& A, int b) {
// 保存计算结果
vector<int> C;
// 保存来自上一位的进位值
int t = 0;
// 从低位到高位遍历A计算乘法结果
for (int i = 0; i < A.size(); i ++ ) {
// 把A的这一位A[i]乘以b的结果加上上一位的进位值t
t += A[i] * b;;
// 模10的结果就是这一位的值
C.push_back(t % 10);
// 除以10就是向高位的进位值
t /= 10;
}
// 最后的进位可能很大,要处理剩余的进位
while (t) {
// 每次模10的结果构成新的一位
C.push_back(t % 10);
// 然后除以10作为向高位的进位值
t /= 10;
}
// 处理高位的连续0
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main() {
// 读取大整数a和普通量级的整数b
string a;
int b;
cin >> a >> b;
// a从低位到高位存到A里
vector<int> A;
for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
// 高精度乘法计算A*b
auto C = mul(A, b);
// 从高位到低位输出结果
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
cout << endl;
return 0;
}
4 高精度整数A/b
模板题:高精度除法。
这里 A A A是一个高精度整数, b b b是一个普通精度的整数,计算它们的除法也是用列竖式的方法,但是因为 b b b不是一个高精度的数,只需要从 A A A高位到低位(注意这里和前面都不一样),每次把对应位置的数字 A [ i ] A[i] A[i]加上来自高位的余数 r ∗ 10 r * 10 r∗10再除以 b b b就可以了,而不需要把 b b b的每一位拆开来。
这里高位的余数 r r r是在高位那个量级上的,所以在这一位(较低一位)上要乘以 10 10 10才行。
和前面保持一致,
A
A
A还是从低位到高位来存,但是计算的时候从高位向低位依次做除法,得到的结果
C
C
C是从高位到低位存的。所以还要记得把
C
C
C用reverse
翻转一下,变成从低位到高位存的,然后还是要删除一下高位的连续
0
0
0。
在代码实现中,函数div
的返回值返回的是
A
A
A除以
b
b
b的商,而余数
r
r
r从参数表里用引用的形式返回。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
// 高精度除法,计算大整数A和普通量级的整数b的除法结果
// A从低位到高位存储,商从返回值返回,余数从形参表返回
vector<int> div(vector<int>& A, int b, int& r) {
// 保存商
vector<int> C;
// 从A的高位到低位
for (int i = A.size() - 1; i >= 0; i -- ) {
// 高一位传下来的余数r,乘以10变成这一位的量级,再加上当前位的值A[i]
int val = r * 10 + A[i];
// 除以b的结果就放在C新的一位上
C.push_back(val / b);
// 除完之后,对b取模就是这一位留下的余数
// 如果A没走完,就会在下一位处理;如果A走完了,就会从形参表引用返回
r = val % b;
}
// 由于计算结果商C是从高位到低位存的,这里把它翻转成从低位向高位存的
reverse(C.begin(), C.end());
// 去除高位的连续0,再返回
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main() {
// 读入大整数a和普通量级的整数b
string a;
int b;
cin >> a >> b;
// 把A从低位到高位存储(和其它四则运算保持一致)
vector<int> A;
for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
// 计算A/b的结果,商为C,余数是r
int r = 0;
auto C = div(A, b, r);
// 从高位到地位输出商C,输出余数r
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
cout << endl << r << endl;
return 0;
}