CC++ & Algorithm

高精度加法——像列竖式一样计算超大数

较难29
语言版本:C++Python
概述:用数组模拟手算加法,解决普通整型装不下的超大数相加问题。

高精度加法:像列竖式一样计算超大数

小朋友,你有没有想过,如果两个数字特别特别大,比如有100位、1000位,甚至连C++的unsigned long long都装不下,那要怎么把它们加起来呢?这时候就需要“高精度加法”来帮忙了。

高精度加法就像我们平时列竖式做加法一样:把两个数字的每一位对齐,从最右边(个位)开始,一位一位地加,如果满10就向前进1。在C++里,我们用数组或vector来存这些数位,而且为了方便进位,我们通常把个位放在数组的第0个位置(下标0),十位放在第1个位置,依此类推。

为什么要用高精度加法?

普通整数类型(如intlong 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;
}

运行示例

  • 输入:999999999999999999991(两个20位的数)

  • 输出:100000000000000000000(1后面20个0,共21位)

  • 输入:1234567890123456789098765432109876543210

  • 输出:111111111011111111100(注意进位)

你可以自己试试更大的数字,比如输入两个100位的数,程序也能瞬间算出结果。

延伸学习

学完了高精度加法,你还可以继续探索:

  • 高精度减法:比加法多一个“借位”的处理,注意结果可能为负。
  • 高精度乘法:需要两层循环,像竖式乘法一样逐位相乘再累加。
  • 高精度除法:更复杂,模拟竖式除法,需要逐位试商。
  • 大数比较:先比位数,再比每一位。

高精度计算是竞赛中常见的基础技巧,掌握了它,你就可以处理任意大的整数了。快去试试吧!

例题精讲

1单选题

高精度加法中,若两个大数分别用数组a和b存储(a[0]存最低位),相加结果存入数组c,下列哪项是核心循环的正确写法?

Afor(i=0;i<len;i++){c[i]+=a[i]+b[i];c[i+1]+=c[i]/10;c[i]%=10;}
Bfor(i=0;i<len;i++){c[i]=a[i]+b[i];c[i+1]=c[i]/10;c[i]%=10;}
Cfor(i=0;i<len;i++){c[i]=a[i]+b[i]+c[i];c[i+1]=c[i]/10;c[i]%=10;}
Dfor(i=0;i<len;i++){c[i]=a[i]+b[i];c[i+1]+=c[i]/10;c[i]%=10;}
2判断题

高精度加法中,将两个大数用字符串读入后,应先将字符串中的字符转换为整数并逆序存入整型数组,然后进行逐位相加并处理进位。

3填空题
// 高精度加法函数,返回结果字符串
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;
}