高精度加法——像列竖式一样计算超大数
较难29高精度加法:像列竖式一样计算超大数
小朋友,你有没有想过,如果两个数字特别特别大,比如有100位、1000位,甚至连C++的unsigned long long都装不下,那要怎么把它们加起来呢?这时候就需要“高精度加法”来帮忙了。
高精度加法就像我们平时列竖式做加法一样:把两个数字的每一位对齐,从最右边(个位)开始,一位一位地加,如果满10就向前进1。在C++里,我们用数组或vector来存这些数位,而且为了方便进位,我们通常把个位放在数组的第0个位置(下标0),十位放在第1个位置,依此类推。
为什么要用高精度加法?
普通整数类型(如int、long long)能表示的数字范围有限。比如:
int最大约 21 亿(10位数)long long最大约 9.22×10¹⁸(19位数)unsigned long long最大约 1.84×10¹⁹(20位数)
但生活中我们可能会遇到更大的数:比如全世界的货币总额、宇宙中恒星的数量、或者小明攒了100年的零花钱——假设他每天攒1元,100年也才36500元,但如果我们把全中国14亿人每天的零花钱加起来,数字就远远超过20位了。这时候只能用高精度加法,把数字当字符串读入,再一位一位地加。
竖式加法与数组存储——倒着放更方便
还记得小学二年级学的竖式加法吗?比如计算 123 + 456:
1 2 3
+ 4 5 6
---------
5 7 9
我们从个位开始加:3+6=9,十位:2+5=7,百位:1+4=5。在C++里,我们把数字倒过来存进数组,这样个位就在下标0,十位在下标1,百位在下标2。为什么倒着存? 因为加法是从低位到高位进行的,倒着存可以让我们从数组开头一直往后处理,最后进位也方便在末尾添加新位。
举个生活中的例子:假设你零花钱是 99999999999999999999 元(20个9),你妈妈又给了你 1 元。用普通整数根本存不下这么大的数,但用高精度加法,就像列竖式一样:
9 9 9 ... 9 9 9 (20个9)
+ 1
-------------------------
1 0 0 0 ... 0 0 0 (1后面20个0)
倒着存进去:a = [9,9,9,...,9](20个9),b = [1],然后从下标0开始加,最终结果会多出一位进位。
一步一步实现高精度加法
下面我们拆解代码的每一步。
1. 用字符串读入大数
因为整数类型存不下,所以用string读入。比如输入 "12345678901234567890" 作为字符串。
2. 把字符串转换成倒序的vector(数字数组)
从字符串的最后一位(个位)开始,把每个字符转成数字(减'0'),依次放入vector。
string s = "12345";
vector<int> a; // 存放倒序的数字
for (int i = s.size() - 1; i >= 0; i--) {
a.push_back(s[i] - '0'); // 字符转数字,从个位开始存
}
// 结果是 a = [5,4,3,2,1]
3. 写加法函数
模仿竖式加法,逐位相加,处理进位。
vector<int> add(vector<int> &a, vector<int> &b) {
vector<int> c; // 存放和的结果(也是倒序)
int carry = 0; // 进位,初始为0
int len = max(a.size(), b.size()); // 取较长的位数
for (int i = 0; i < len; i++) {
int va = (i < a.size()) ? a[i] : 0; // 如果a有这一位就取,否则0
int vb = (i < b.size()) ? b[i] : 0; // 同理
int sum = va + vb + carry; // 当前位相加 + 进位
c.push_back(sum % 10); // 当前位的结果(取个位)
carry = sum / 10; // 新的进位(整除10)
}
if (carry > 0) { // 最后还有进位,多加一位
c.push_back(carry);
}
return c;
}
4. 最后输出结果
因为结果在vector里也是低位在前(下标0是个位),所以我们要从后往前输出,才是正常的数字顺序。
for (int i = c.size() - 1; i >= 0; i--) {
cout << c[i];
}
cout << endl;
小心!这些错误容易犯
新手写高精度加法时,常常会遇到以下几个坑:
❌ 忘记处理最后的进位
比如 99 + 1,竖式:个位 9+1=10,写0进1,十位 9+0+1=10,写0进1,结果应该是100。如果忘记最后还有一个进位,就会输出"00",少了百位的1。一定要在循环结束后检查carry是否为1,是的话要push_back(1)。
❌ 字符串转数字时忘记减'0'
字符'5'的ASCII码是53,直接push_back(s[i])会把53存进去,而不是5。一定要用s[i] - '0'。
❌ 输出顺序搞反
结果数组低位在前(个位在下标0),如果直接从头输出,会得到反着的数字。比如结果数组[0,0,1]应该输出"100",如果顺序输出就是"001"。必须倒着输出。
❌ 数组下标越界
在加法循环中,要检查i是否小于a.size()和b.size(),如果超出,就当作0处理。不能直接访问a[i]而不判断。
完整代码试一试
下面是一段完整可运行的代码,它从键盘读入两个很大的整数(字符串形式),输出它们的和。代码中每一行都有中文注释,方便你理解。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
// 高精度加法:返回 a+b 的结果(用vector存,低位在前)
vector<int> add(vector<int> &a, vector<int> &b) {
vector<int> c; // 存放结果的vector(倒序)
int carry = 0; // 进位,初始为0
int len = max(a.size(), b.size()); // 取较长的位数
for (int i = 0; i < len; i++) {
// 如果当前位存在,就取该位的值,否则取0
int va = (i < a.size()) ? a[i] : 0;
int vb = (i < b.size()) ? b[i] : 0;
int sum = va + vb + carry; // 当前位相加(含进位)
c.push_back(sum % 10); // 当前位的结果(取个位)
carry = sum / 10; // 新的进位(取十位)
}
if (carry > 0) { // 最后还有进位,多加一位
c.push_back(carry);
}
return c;
}
int main() {
string s1, s2; // 用字符串读入两个大整数
cin >> s1 >> s2; // 输入:比如 99999999999999999999 和 1
vector<int> a, b; // 存放两个数的倒序数字数组
// 从字符串最后一位开始,把数字倒着存进vector
for (int i = s1.size() - 1; i >= 0; i--) {
a.push_back(s1[i] - '0'); // 字符转数字,个位在前
}
for (int i = s2.size() - 1; i >= 0; i--) {
b.push_back(s2[i] - '0');
}
vector<int> c = add(a, b); // 调用加法函数
// 结果要倒着输出(因为低位在前,输出要从最高位开始)
for (int i = c.size() - 1; i >= 0; i--) {
cout << c[i];
}
cout << endl;
return 0;
}
运行示例
-
输入:
99999999999999999999和1(两个20位的数) -
输出:
100000000000000000000(1后面20个0,共21位) -
输入:
12345678901234567890和98765432109876543210 -
输出:
111111111011111111100(注意进位)
你可以自己试试更大的数字,比如输入两个100位的数,程序也能瞬间算出结果。
延伸学习
学完了高精度加法,你还可以继续探索:
- 高精度减法:比加法多一个“借位”的处理,注意结果可能为负。
- 高精度乘法:需要两层循环,像竖式乘法一样逐位相乘再累加。
- 高精度除法:更复杂,模拟竖式除法,需要逐位试商。
- 大数比较:先比位数,再比每一位。
高精度计算是竞赛中常见的基础技巧,掌握了它,你就可以处理任意大的整数了。快去试试吧!
例题精讲
高精度加法中,若两个大数分别用数组a和b存储(a[0]存最低位),相加结果存入数组c,下列哪项是核心循环的正确写法?
高精度加法中,将两个大数用字符串读入后,应先将字符串中的字符转换为整数并逆序存入整型数组,然后进行逐位相加并处理进位。
// 高精度加法函数,返回结果字符串
string add(string s1, string s2) {
int a[505]={0}, b[505]={0}, c[505]={0};
int len1 = s1.size(), len2 = s2.size();
for (int i=0; i<len1; i++) a[i] = s1[len1-1-i] - '0';
for (int i=0; i<len2; i++) b[i] = s2[len2-1-i] - '0';
int len = max(len1, len2);
for (int i=0; i<len; i++) {
c[i] += a[i] + b[i];
if (c[i] >= 10) {
c[i+1] += ___;
c[i] -= 10;
}
}
if (c[len] > 0) len++;
string ans = "";
for (int i=len-1; i>=0; i--) ans += (char)(c[i] + '0');
return ans;
}