当两个数大到连 long long 都装不下时,用数组模拟小学竖式加法的过程,逐位相加、逐位进位。
C++ 里最大的整数类型 long long 能表示的范围大约是 ±9.2 × 10¹⁸,也就是最多约 19 位十进制数字。但有些题目要求计算的数字远远超出这个范围——比如求 100!(100 的阶乘,结果有 158 位)、或者直接给出两个几百位的大数相加。这时候任何内置的整数类型都装不下,需要另想办法。
高精度算法的核心思想很朴素:既然一个变量存不下这么大的数,那就用数组存,一个数组元素存一位数字,就像小学时用竖式做加法一样,一位一位地算、一位一位地进位。这一节先从最简单的高精度加法入手,后面几节的减法、乘法、除法都建立在同样的思路上。
输入的大数通常是以字符串形式给出的(比如 "12345"),需要先把它转换成一个数字数组。这里有一个关键的约定:数组下标 0 存个位,下标越大存的位越高——也就是"倒着存"。
| 1 | string s; |
| 2 | cin >> s; // 输入 "12345" |
| 3 | |
| 4 | int len = s.size(); |
| 5 | vector<int> a(len, 0); // 长度按实际位数开,不需要猜一个固定大小 |
| 6 | |
| 7 | for (int i = 0; i < len; i++) |
| 8 | { |
| 9 | a[i] = s[len - 1 - i] - '0'; // 从字符串末尾开始取,字符转数字见 2.2 节 |
| 10 | } |
int a[505] 必须提前猜一个足够大的固定长度,容易开小导致越界、开太大又浪费空间;vector<int> a(len, 0) 则是按实际输入的位数精确分配大小,且所有元素自动初始化成 0(免去手动清零),后面遇到长度不确定或需要动态调整大小的场景(比如接下来处理两个位数不同的数),vector 明显更省心。用法上 a[i] 和普通数组完全一样,几乎不需要额外学习成本。存好数组之后,加法就是模拟小学竖式:从个位(下标 0)开始,每一位对应相加,满 10 就往高一位进 1,一直算到两个数都用完为止。
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),不需要特殊处理。| 1 | string s1, s2; |
| 2 | cin >> s1 >> s2; // 用字符串读入两个大数 |
| 3 | |
| 4 | int len1 = s1.size(), len2 = s2.size(); |
| 5 | int len = max(len1, len2); // 取较长的位数,作为两个数组的统一长度 |
| 6 | |
| 7 | vector<int> a(len, 0), b(len, 0), c(len + 1, 0); // c 多开一位,留给最高位可能的进位 |
| 8 | |
| 9 | for (int i = 0; i < len1; i++) |
| 10 | { |
| 11 | a[i] = s1[len1 - 1 - i] - '0'; |
| 12 | } |
| 13 | for (int i = 0; i < len2; i++) |
| 14 | { |
| 15 | b[i] = s2[len2 - 1 - i] - '0'; |
| 16 | } |
| 17 | |
| 18 | int carry = 0; // 进位 |
| 19 | for (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 | |
| 26 | if (carry) |
| 27 | { |
| 28 | c[len] = carry; // 最高位还有进位,多开的一位正好派上用场 |
| 29 | len++; |
| 30 | } |
| 31 | |
| 32 | // 结果是倒着存的,从高位(len-1)往低位(0)输出才是正确顺序 |
| 33 | for (int i = len - 1; i >= 0; i--) |
| 34 | { |
| 35 | cout << c[i]; |
| 36 | } |
把本节内容汇总成几条最容易踩坑的规则:
99+1=100),循环结束后如果 carry 还不是 0,必须再多存一位,否则结果会少一位数字。c 必须开到 len+1,而不是 len——两数相加的结果最多比原来两数中较长的那个多出一位(比如 99+1=100)。如果 c 只开了 len 大小,c[len] = carry 这一行会访问到 vector 范围之外的位置,属于未定义行为,同样需要格外小心。len-1 递减到 0,而不是从 0 递增——这是最容易顺手写反的地方。