在算法竞赛和实际项目中,经常遇到两个超大整数相减的场景。C++内置的整型中,即使是unsigned long long,也只能表示到约1.8乘以10的19次方,一旦数值超出这个范围,直接相减就会得到错误结果。解决这个问题的标准做法是高精度减法:把大数当作字符串读入,再模拟人工竖式减法逐位计算。本文将围绕借位处理和结果符号判断这两个核心难点,给出完整的实现思路和源码。

高精度减法的基本原理
高精度减法的思路与手工竖式减法完全一致。假设我们要计算两个正整数A减B,首先把两个数按位拆开存储,通常用数组或vector存储,每一位存放一个0到9的数字。为了方便从低位向高位逐位计算,一般会把字符串反转,让下标0对应个位。
逐位相减时,核心问题在于某一位上被减数的数字可能小于减数的数字,此时需要向高位借位。具体做法是:先看当前位是否已经被上一位借走过1,如果被借过,当前位的值要先减1;然后与减数对应位相减,若结果为负,就加10并向下一位借1。这个过程持续到最高位处理完毕。
用数学表达来描述就是:设t为当前位计算后的临时结果,t = a[i] - b[i] - borrow,若t小于0,则本位结果为t + 10,且borrow置为1;否则本位结果为t,borrow置为0。这个借位变量贯穿整个循环,是高精度减法最关键的细节。
结果符号的判断与处理
高精度减法与加法最大的区别在于:减法可能得到负数。如果A小于B,直接模拟减法会得到一堆负数位,结果错误。因此计算前必须先比较A和B的大小关系。
比较两个大数的方法很简单:先比长度,长度更长的数一定更大;长度相同时,从最高位向低位逐位比较,第一个不相等的位置上数字大的那个数更大。若A大于等于B,直接计算A减B,结果为非负;若A小于B,则计算B减A,并在结果前面补一个负号。
这种处理方式把负数减法统一转化为了大数减小数的减法,逻辑清晰且不易出错。特别提醒一种边界情况:当A等于B时,结果应该输出0而不是空串,后面去前导零时要单独处理。
完整源码实现
下面给出一份完整可编译运行的C++代码,包括比较函数、减法核心函数和主函数。代码使用vector存储每一位数字,读入后先反转字符串再转存,符合从低位到高位计算的习惯。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
// 比较函数:判断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; // 完全相等,返回true
}
// 核心减法:要求A >= B,逐位相减并处理借位
vector<int> sub(vector<int>& A, vector<int>& B) {
vector<int> C;
int borrow = 0; // 借位标记
for (int i = 0; i < (int)A.size(); i++) {
int t = A[i] - borrow;
if (i < (int)B.size()) t -= B[i];
if (t < 0) {
t += 10; // 不够减,借一位
borrow = 1;
} else {
borrow = 0;
}
C.push_back(t);
}
// 去除前导零(高位在数组尾部)
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main() {
string a, b;
cin >> a >> b;
vector<int> A, 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');
vector<int> C;
if (cmp(A, B)) {
C = sub(A, B); // A >= B,结果非负
} else {
C = sub(B, A); // A < B,交换后计算并补负号
cout << "-";
}
// 从高位到低位输出
for (int i = C.size() - 1; i >= 0; i--) cout << C[i];
cout << endl;
return 0;
}
代码中有几个值得注意的细节。第一,cmp函数比较长度相同的数时,是从数组末尾向前比较的,因为数组末尾对应的是最高位。第二,sub函数中借位变量borrow只在结果为负时置1,否则必须清零,否则会把借位错误地传递下去。第三,去前导零的while循环保留了至少一位,这样两个相等的数相减会输出0而不是空白。
常见错误与优化建议
实现高精度减法时最容易犯三类错误。一是忘记比较大小就直接相减,当被减数小于减数时输出乱码;二是借位后忘记判断下一位是否还需要再借,导致中间某位出现负数;三是去除前导零时把结果0也删掉了,导致输出为空。写完代码后建议用几组特殊数据测试,比如100减99、1000减1、相同的数相减等。
如果需要处理带负数的完整减法,可以在外层封装符号逻辑:正减正按本文方法处理;负减负相当于交换后相减;一正一负则转化为高精度加法。也就是根据两个操作数的符号组合,分别路由到加法或减法函数,并单独维护结果符号,这样就能支持任意整数的四则运算扩展。
性能方面,这种逐位模拟的时间复杂度是O(n),n为大数的位数,对于几万位的数字也能在毫秒级完成。如果追求更高效率,可以采用压位技巧,即每个数组元素存储4到9位数字,相当于把进制从10提升到10000甚至更高,能显著减少循环次数和内存占用,这也是竞赛中常用的优化手段。