和加法一样用数组模拟竖式运算,但这次要处理的是"借位",还要先弄清楚两个数到底谁比较大。
高精度减法沿用了上一节(1.9 加法)"数组倒序存储 + 逐位运算"的整体框架,但比加法多出两个新问题:
15 - 489),单纯按位相减会出现"不够减"的情况,需要提前判断好两数的大小关系;为了避免结果出现负数,高精度减法通常约定"用大数减小数",如果被减数本来就比减数小,就先交换两者、算出差值后再在结果前面添上负号。判断两个高精度数谁大谁小,规则很直观:
1500 是 4 位,489 是 3 位,1500 直接胜出,不需要再比第二条;确定了"大数 - 小数"之后,就可以从个位开始逐位相减。如果某一位不够减,就向前一位(更高的那一位)借 1 当 10 用,同时把那一位自己减 1(因为被借走了)。
2 - 9 不够减,向十位借 1 当 10 用,本位变成 2+10=12,算出 12-9=3;十位借出 1 之后从 5 变成 4,再算 4-2=2(够减,不用再向百位借);百位没有被借位影响,直接 3-1=2。三位拼起来是 223,正好等于 352-129。4 ≥ 2)。但如果十位借出 1 之后反而变得不够减(比如原本是 504 - 89,十位是 0,借出 1 后变成 -1),就要继续向百位借,借位可以像这样一路向更高位传递,直到借到足够为止——这也是为什么代码里的借位逻辑要放在循环里,每一位都要重新判断一次是否需要借位,而不是只判断一次。先写一个独立的比较函数 IsLess,再串联成完整流程:读入两数 → 比较大小并在必要时交换 → 按位相减(带借位)→ 去掉前导 0 → 按符号输出。
| 1 | // 判断 a 是否小于 b(a、b 均为倒序存储的数字 vector) |
| 2 | bool 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 | } |
| 1 | string s1, s2; |
| 2 | cin >> s1 >> s2; |
| 3 | |
| 4 | int len1 = s1.size(), len2 = s2.size(); |
| 5 | vector<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 | |
| 16 | bool isNegative = false; |
| 17 | if (IsLess(a, b)) |
| 18 | { |
| 19 | swap(a, b); // 交换后保证 a 是较大的数 |
| 20 | isNegative = true; // 原本是小数减大数,结果要带负号 |
| 21 | } |
| 22 | b.resize(a.size(), 0); // 用 0 把 b 补到和 a 一样长,避免按位访问时越界 |
| 23 | |
| 24 | vector<int> c(a.size(), 0); |
| 25 | for (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) |
| 37 | int len = (int)c.size(); |
| 38 | while (len > 1 && c[len - 1] == 0) |
| 39 | { |
| 40 | len--; |
| 41 | } |
| 42 | |
| 43 | // 特判:如果结果本身是 0,就不应该再输出负号(不存在"负0") |
| 44 | if (isNegative && !(len == 1 && c[0] == 0)) |
| 45 | { |
| 46 | cout << "-"; |
| 47 | } |
| 48 | |
| 49 | for (int i = len - 1; i >= 0; i--) |
| 50 | { |
| 51 | cout << c[i]; |
| 52 | } |
a、b 长度可以按各自输入的实际位数分配,不需要先猜一个固定大小;二是交换较大数之后,直接用 b.resize(a.size(), 0) 把较短的那个数用 0 补到和 a 一样长,之后 a[i] - b[i] 就能安全地按位访问,不用担心下标越界——这一步如果用普通数组,因为数组本身已经有固定大小、天然"自带"补 0,反而不用特地处理;但换成 vector 后,必须自己动手 resize 补齐,这是使用 vector 时容易漏掉的一个细节。489 - 489),即使一开始判断出"需要交换、结果为负",也不应该在最终输出前打印负号——"负0"不符合正常的数字表达习惯,输出前必须加上这个特判。把本节内容汇总成几条最容易踩坑的规则:
504-500=4,但存储的数组高位是 0,0,4),如果不去掉多余的高位 0,直接按数组存储位数输出会变成"004",还带着两个多余的 0。