← 目录 / 算法文档 · 模块一 数学基础 / 1.10 高精度减法

1.10 高精度减法

和加法一样用数组模拟竖式运算,但这次要处理的是"借位",还要先弄清楚两个数到底谁比较大。

本页目录
① 减法比加法多了什么

高精度减法沿用了上一节(1.9 加法)"数组倒序存储 + 逐位运算"的整体框架,但比加法多出两个新问题:

⚠️
两个新麻烦:
1️⃣ 结果可能是负数——如果被减数比减数小(比如 15 - 489),单纯按位相减会出现"不够减"的情况,需要提前判断好两数的大小关系;
2️⃣ 某一位不够减时要向高位"借位"——这和加法的"进位"刚好相反,进位是"往高位送 1",借位是"从高位借 1 用"。
② 第一步:判断两数大小

为了避免结果出现负数,高精度减法通常约定"用大数减小数",如果被减数本来就比减数小,就先交换两者、算出差值后再在结果前面添上负号。判断两个高精度数谁大谁小,规则很直观:

489
vs
1500
📏
两条比较规则,按顺序判断:
1️⃣ 先比位数——位数多的数一定更大(没有前导 0 的情况下)。上面例子里 1500 是 4 位,489 是 3 位,1500 直接胜出,不需要再比第二条;
2️⃣ 位数相同则从最高位开始逐位比较——哪个数先出现更大的数字,哪个数就更大(一旦分出胜负立刻停止比较)。
③ 竖式减法的思路:借位

确定了"大数 - 小数"之后,就可以从个位开始逐位相减。如果某一位不够减,就向前一位(更高的那一位)借 1 当 10 用,同时把那一位自己减 1(因为被借走了)。

竖式演示:352 − 129 = 223
·
3
1
2
百位
借出 1
54
2
2
十位
借入 1
212
9
3
个位
从个位开始:个位 2 - 9 不够减,向十位借 1 当 10 用,本位变成 2+10=12,算出 12-9=3;十位借出 1 之后从 5 变成 4,再算 4-2=2(够减,不用再向百位借);百位没有被借位影响,直接 3-1=2。三位拼起来是 223,正好等于 352-129
🔗
如果十位自己也不够减怎么办?上面这个例子只借了一次位,十位借出 1 后还有富余(4 ≥ 2)。但如果十位借出 1 之后反而变得不够减(比如原本是 504 - 89,十位是 0,借出 1 后变成 -1),就要继续向百位借,借位可以像这样一路向更高位传递,直到借到足够为止——这也是为什么代码里的借位逻辑要放在循环里,每一位都要重新判断一次是否需要借位,而不是只判断一次。
④ 完整代码实现

先写一个独立的比较函数 IsLess,再串联成完整流程:读入两数 → 比较大小并在必要时交换 → 按位相减(带借位)→ 去掉前导 0 → 按符号输出。

C++ · 比较大小函数
1// 判断 a 是否小于 b(a、b 均为倒序存储的数字 vector)
2bool IsLess(vector<int>& a, vector<int>& b)
3{
4 if (a.size() != b.size())
5 {
6 return a.size() < b.size(); // 规则①:位数少的更小
7 }
8 for (int i = (int)a.size() - 1; i >= 0; i--) // 规则②:从最高位往低位比
9 {
10 if (a[i] != b[i])
11 {
12 return a[i] < b[i];
13 }
14 }
15 return false; // 位数相同、每一位都相等,说明两数相等
16}
C++ · 高精度减法主流程
1string s1, s2;
2cin >> s1 >> s2;
3
4int len1 = s1.size(), len2 = s2.size();
5vector<int> a(len1, 0), b(len2, 0);
6
7 for (int i = 0; i < len1; i++)
8 {
9 a[i] = s1[len1 - 1 - i] - '0';
10 }
11 for (int i = 0; i < len2; i++)
12 {
13 b[i] = s2[len2 - 1 - i] - '0';
14 }
15
16bool isNegative = false;
17if (IsLess(a, b))
18{
19 swap(a, b); // 交换后保证 a 是较大的数
20 isNegative = true; // 原本是小数减大数,结果要带负号
21}
22b.resize(a.size(), 0); // 用 0 把 b 补到和 a 一样长,避免按位访问时越界
23
24vector<int> c(a.size(), 0);
25for (int i = 0; i < (int)a.size(); i++)
26{
27 a[i] -= b[i]; // 本位相减
28 if (a[i] < 0) // 不够减,向高位借 1
29 {
30 a[i] += 10; // 本位加 10(借 1 当 10 用)
31 a[i + 1] -= 1; // 高一位被借走 1
32 }
33 c[i] = a[i];
34}
35
36// 去掉结果高位多余的 0(例如 504-500=004,应当只输出 4)
37int len = (int)c.size();
38while (len > 1 && c[len - 1] == 0)
39{
40 len--;
41}
42
43// 特判:如果结果本身是 0,就不应该再输出负号(不存在"负0")
44if (isNegative && !(len == 1 && c[0] == 0))
45{
46 cout << "-";
47}
48
49for (int i = len - 1; i >= 0; i--)
50{
51 cout << c[i];
52}
🧰
这里 vector 帮了两个忙:一是 ab 长度可以按各自输入的实际位数分配,不需要先猜一个固定大小;二是交换较大数之后,直接用 b.resize(a.size(), 0) 把较短的那个数用 0 补到和 a 一样长,之后 a[i] - b[i] 就能安全地按位访问,不用担心下标越界——这一步如果用普通数组,因为数组本身已经有固定大小、天然"自带"补 0,反而不用特地处理;但换成 vector 后,必须自己动手 resize 补齐,这是使用 vector 时容易漏掉的一个细节。
4
忘记特判"结果为 0"时不能带负号:如果两数相减结果恰好是 0(比如 489 - 489),即使一开始判断出"需要交换、结果为负",也不应该在最终输出前打印负号——"负0"不符合正常的数字表达习惯,输出前必须加上这个特判。
⑤ 常见陷阱

把本节内容汇总成几条最容易踩坑的规则:

1
没有提前判断大小就直接相减:如果不确认被减数≥减数就直接按位相减,借位会一路借到最高位之外,得到完全错误的结果。务必先比较,让"大数减小数",符号单独在最后处理(如果原本是小数减大数,结果记得添加负号)。
2
忘记去掉结果的前导 0:减法很容易产生结果比原来位数少的情况(比如 504-500=4,但存储的数组高位是 0,0,4),如果不去掉多余的高位 0,直接按数组存储位数输出会变成"004",还带着两个多余的 0。
3
比较函数只比了位数,忘记位数相同的情况:判断大小的两条规则缺一不可——位数不同直接看位数,但位数相同时必须老老实实从最高位开始逐位比较,不能偷懒假设"位数相同就相等"。
🏆
接下来:高精度乘法(1.11 节)不再需要处理借位或大小比较,但会引入一个新概念——两个数各自某一位相乘的结果,要按"位置相加"的规则落到正确的结果位上,思路和加减法略有不同。