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

1.9 高精度加法

当两个数大到连 long long 都装不下时,用数组模拟小学竖式加法的过程,逐位相加、逐位进位。

本页目录
① 为什么需要高精度

C++ 里最大的整数类型 long long 能表示的范围大约是 ±9.2 × 10¹⁸,也就是最多约 19 位十进制数字。但有些题目要求计算的数字远远超出这个范围——比如求 100!(100 的阶乘,结果有 158 位)、或者直接给出两个几百位的大数相加。这时候任何内置的整数类型都装不下,需要另想办法。

高精度算法的核心思想很朴素:既然一个变量存不下这么大的数,那就用数组存,一个数组元素存一位数字,就像小学时用竖式做加法一样,一位一位地算、一位一位地进位。这一节先从最简单的高精度加法入手,后面几节的减法、乘法、除法都建立在同样的思路上。

② 用数组存储大数

输入的大数通常是以字符串形式给出的(比如 "12345"),需要先把它转换成一个数字数组。这里有一个关键的约定:数组下标 0 存个位,下标越大存的位越高——也就是"倒着存"。

字符串 "12345" 转换成数字数组(下标 0 存个位)
5
[0]
个位
4
[1]
十位
3
[2]
百位
2
[3]
千位
1
[4]
万位
为什么要倒着存?因为加法进位是"从低位往高位"传递的——个位进位会影响十位,十位进位会影响百位……如果下标 0 存最高位(正着存),每次进位都要往数组更小的方向找位置,还要提前预留最高位可能因为进位而多出一位的空间,处理起来别扭得多。倒着存,下标只会一路递增,进位时直接往后面写就行,代码更简洁。
C++ · 字符串转数字数组(倒序存储)
1string s;
2cin >> s; // 输入 "12345"
3
4int len = s.size();
5vector<int> a(len, 0); // 长度按实际位数开,不需要猜一个固定大小
6
7for (int i = 0; i < len; i++)
8{
9 a[i] = s[len - 1 - i] - '0'; // 从字符串末尾开始取,字符转数字见 2.2 节
10}
🧰
为什么用 vector 而不是普通数组?普通数组 int a[505] 必须提前猜一个足够大的固定长度,容易开小导致越界、开太大又浪费空间;vector<int> a(len, 0) 则是按实际输入的位数精确分配大小,且所有元素自动初始化成 0(免去手动清零),后面遇到长度不确定或需要动态调整大小的场景(比如接下来处理两个位数不同的数),vector 明显更省心。用法上 a[i] 和普通数组完全一样,几乎不需要额外学习成本。
③ 竖式加法的思路

存好数组之后,加法就是模拟小学竖式:从个位(下标 0)开始,每一位对应相加,满 10 就往高一位进 1,一直算到两个数都用完为止。

竖式演示:4 8 9 + 1 5 = 5 0 4
1
4
8
5
百位
1
8
1
0
十位
0
9
5
4
个位
从右往左(个位→十位→百位)逐位相加:个位 9+5=14,写 4 进 1;十位 8+1+1(进位)=10,写 0 进 1;百位 4+0+1(进位)=5,写 5。三步拼起来就是 504,和真实结果 489+15=504 完全一致。
📖
两个数位数不一样怎么办?竖式加法允许两个数长度不同(比如 489 + 5),位数较短的那个数,多出来的高位就当作 0 参与运算即可——代码里只要让循环跑到"两个数中较长的那个"的位数,某一个数组越界的部分自然就是 0(因为数组一开始整体初始化成了 0),不需要特殊处理。
④ 完整代码实现
C++ · 高精度加法
1string s1, s2;
2cin >> s1 >> s2; // 用字符串读入两个大数
3
4int len1 = s1.size(), len2 = s2.size();
5int len = max(len1, len2); // 取较长的位数,作为两个数组的统一长度
6
7vector<int> a(len, 0), b(len, 0), c(len + 1, 0); // c 多开一位,留给最高位可能的进位
8
9for (int i = 0; i < len1; i++)
10{
11 a[i] = s1[len1 - 1 - i] - '0';
12}
13for (int i = 0; i < len2; i++)
14{
15 b[i] = s2[len2 - 1 - i] - '0';
16}
17
18int carry = 0; // 进位
19for (int i = 0; i < len; i++)
20{
21 int sum = a[i] + b[i] + carry; // 本位相加,别忘了加上进位
22 c[i] = sum % 10; // 本位只留个位数字
23 carry = sum / 10; // 剩下的部分作为进位,传给下一位
24}
25
26if (carry)
27{
28 c[len] = carry; // 最高位还有进位,多开的一位正好派上用场
29 len++;
30}
31
32// 结果是倒着存的,从高位(len-1)往低位(0)输出才是正确顺序
33for (int i = len - 1; i >= 0; i--)
34{
35 cout << c[i];
36}
⑤ 常见陷阱

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

1
忘记处理最后一次进位:两数相加,结果的位数可能比原来两个数都多一位(比如 99+1=100),循环结束后如果 carry 还不是 0,必须再多存一位,否则结果会少一位数字。
2
vector 长度没有留出进位需要的那一位:结果数组 c 必须开到 len+1,而不是 len——两数相加的结果最多比原来两数中较长的那个多出一位(比如 99+1=100)。如果 c 只开了 len 大小,c[len] = carry 这一行会访问到 vector 范围之外的位置,属于未定义行为,同样需要格外小心。
3
输出顺序搞反:数组是倒着存的(下标 0 是个位),但输出应该是从高位到低位(符合正常读数顺序),所以输出循环要从 len-1 递减到 0,而不是从 0 递增——这是最容易顺手写反的地方。
🏆
接下来:高精度减法(1.10 节)的整体框架和加法很像,但多了"借位"和"判断两数大小"两个新问题;高精度乘法(1.11 节)和除法(1.12 节)则分别对应竖式乘法和竖式除法,思路都是"先模拟小学算术,再用数组和循环实现"。