Skip to content

第4章 高精度 ​


🎯 本章学习目标 ​

通过本章的学习,你将能够:

  1. 理解高精度概念:知道什么时候需要高精度,理解数组模拟大数的原理
  2. 掌握倒序存储:理解为什么要倒序存储,能正确进行字符串与数组的转换
  3. 实现高精度加法:熟练掌握高精度加法的进位处理
  4. 实现高精度减法:掌握借位处理和大小比较,能去掉前导零
  5. 实现高精度乘法:掌握高精度乘低精度和高精度乘高精度
  6. 实现高精度除法:掌握高精度除以低精度的试商和余数处理
  7. 处理边界情况:能处理进位溢出、前导零、结果为 0 等特殊情况
  8. 分析代码输出:能快速分析高精度代码的中间结果
  9. 应对代码填空:熟练掌握高精度加减乘除的经典代码填空
  10. 解决实际问题:能用高精度算法解决阶乘、大数求和等实际问题

🗺️ 本章知识图谱 ​

高精度运算
├── 基础概念
│   ├── 为什么需要高精度(long long 不够用)
│   ├── 核心思想:数组模拟手工竖式
│   └── 倒序存储(个位在0位,进位方便)
├── 高精度加法
│   ├── 思路:逐位相加 + 进位
│   ├── 步骤:倒序存 → 逐位加 → 处理进位 → 追加最高进位
│   └── 关键:循环结束后 carry!=0 要追加
├── 高精度减法
│   ├── 思路:逐位相减 + 借位
│   ├── 前提:先比较大小,大减小
│   ├── 借位:不够减就向高位借1当10
│   └── 去前导零(结果可能有很多前导零)
├── 高精度乘法
│   ├── 高精 × 低精
│   │   ├── 每一位乘低精度数 + 进位
│   │   └── 进位可能不止一位
│   └── 高精 × 高精
│       ├── 第i位 × 第j位 → 结果第i+j位
│       └── 最后统一处理进位
├── 高精度除法
│   ├── 高精 ÷ 低精
│   │   ├── 正序存储(从高位开始算)
│   │   ├── 余数×10 + 当前位 → 试商
│   │   └── 去前导零
│   └── 余数保留
└── 常见应用
    ├── 大数阶乘
    ├── 斐波那契大数版
    └── 大数求和

一、为什么需要高精度? ​

C++ 基本数据类型有范围限制:

  • int:最大约 2×10⁹
  • long long:最大约 9×10¹⁸

当运算结果超过 long long 范围时(如 100 的阶乘、两个 200 位数的加法),就需要用数组模拟手工竖式运算,这就是高精度算法。

核心存储方式:倒序存储 ​

将数字的个位存在数组下标 0,十位在下标 1,以此类推。这样进位和追加高位都非常方便。

数字:123456

数组(正序):  [1][2][3][4][5][6]   ← 进位需要在数组头部插入,麻烦
数组(倒序):  [6][5][4][3][2][1]   ← 进位直接追加到末尾,方便!
                 ↑个位

二、高精度加法 ​

思路:

  1. 两个数都倒序存入数组
  2. 从低位到高位逐位相加,处理进位
  3. 最后若有进位,追加到结果末尾
cpp
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

// 高精度加法:a + b,输入为字符串,返回结果字符串
string bigAdd(const string& a, const string& b) {
    vector<int> va, vb;
    // 倒序存储:个位在下标 0
    for (int i = a.size() - 1; i >= 0; i--)
        va.push_back(a[i] - '0');
    for (int i = b.size() - 1; i >= 0; i--)
        vb.push_back(b[i] - '0');

    vector<int> result;
    int carry = 0;  // 进位
    int maxLen = max(va.size(), vb.size());
    for (int i = 0; i < maxLen; i++) {
        int sum = carry;
        if (i < (int)va.size()) sum += va[i];
        if (i < (int)vb.size()) sum += vb[i];
        result.push_back(sum % 10);  // 当前位
        carry = sum / 10;             // 进位
    }
    if (carry != 0) result.push_back(carry);  // 处理最高位进位

    // 倒序输出
    string ans;
    for (int i = result.size() - 1; i >= 0; i--)
        ans += (char)('0' + result[i]);
    return ans;
}

int main() {
    string a, b;
    cin >> a >> b;
    cout << bigAdd(a, b) << endl;
    return 0;
}

⚠️ 易错点:

  • 循环结束后不要忘记检查 carry,可能还有最高位的进位!
  • 输入字符串转数字时用 a[i] - '0',不要直接存字符。
  • va.size() 返回 size_t(无符号类型),与 int 比较时建议强转,避免警告。

三、高精度减法 ​

思路:

  1. 先比较两个数的大小,确定结果正负
  2. 用大数减小数,逐位相减,不够减则借位
  3. 去掉结果中的前导零
cpp
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

// 比较两个正数字符串的大小:a >= b 返回 true
bool isGreaterOrEqual(const string& a, const string& b) {
    if (a.size() != b.size()) return a.size() > b.size();
    return a >= b;  // 长度相同时按字典序比较即为数值大小
}

// 高精度减法:a - b(保证 a >= b)
string bigSub(const string& a, const string& b) {
    vector<int> va, vb;
    for (int i = a.size() - 1; i >= 0; i--)
        va.push_back(a[i] - '0');
    for (int i = b.size() - 1; i >= 0; i--)
        vb.push_back(b[i] - '0');

    vector<int> result;
    int borrow = 0;  // 借位
    for (int i = 0; i < (int)va.size(); i++) {
        int diff = va[i] - borrow;
        if (i < (int)vb.size()) diff -= vb[i];
        if (diff < 0) {
            diff += 10;
            borrow = 1;
        } else {
            borrow = 0;
        }
        result.push_back(diff);
    }

    // 去掉前导零(倒序存储下,即去掉末尾的零)
    while (result.size() > 1 && result.back() == 0)
        result.pop_back();

    string ans;
    for (int i = result.size() - 1; i >= 0; i--)
        ans += (char)('0' + result[i]);
    return ans;
}

int main() {
    string a, b;
    cin >> a >> b;
    if (isGreaterOrEqual(a, b)) {
        cout << bigSub(a, b) << endl;
    } else {
        cout << "-" << bigSub(b, a) << endl;
    }
    return 0;
}

⚠️ 易错点:

  • 必须先比较大小,负数结果要加负号,并用大数减小数。
  • 去前导零时注意保留一个 0(当结果为 0 时),不能把所有位都删光!条件 result.size() > 1 保证至少留一位。

四、高精度乘法 ​

高精度乘法分两种:高精度 × 低精度(一个大数乘一个普通整数)和高精度 × 高精度(两个大数相乘)。

4.1 高精度 × 低精度 ​

cpp
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

// 高精度 a 乘以低精度 b(b 为普通整数,0 <= b <= 1e9)
string bigMulSmall(const string& a, int b) {
    if (b == 0) return "0";  // 乘以 0 直接返回
    vector<int> va;
    for (int i = a.size() - 1; i >= 0; i--)
        va.push_back(a[i] - '0');

    vector<int> result;
    long long carry = 0;
    for (int i = 0; i < (int)va.size() || carry != 0; i++) {
        long long product = carry;
        if (i < (int)va.size()) product += (long long)va[i] * b;
        result.push_back(product % 10);
        carry = product / 10;
    }

    string ans;
    for (int i = result.size() - 1; i >= 0; i--)
        ans += (char)('0' + result[i]);
    return ans;
}

int main() {
    string a;
    int b;
    cin >> a >> b;
    cout << bigMulSmall(a, b) << endl;
    return 0;
}

4.2 高精度 × 高精度(竖式乘法) ​

两个大数相乘,逐位相乘后累加到对应位置,最后统一处理进位。

       1 2 3      (倒序存储:[3,2,1])
     ×   4 5      (倒序存储:[5,4])
     -------
         6 1 5    (3×5=15, 2×5=10, 1×5=5)
       4 9 2      (3×4=12, 2×4=8, 1×4=4,左移一位)
     -------
       5 5 3 5

规律:第 i 位 × 第 j 位的结果,累加到第 i+j 位。

cpp
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

// 高精度 × 高精度
string bigMul(const string& a, const string& b) {
    if (a == "0" || b == "0") return "0";
    int n = a.size(), m = b.size();
    vector<int> va(n), vb(m);
    for (int i = 0; i < n; i++) va[i] = a[n - 1 - i] - '0';
    for (int i = 0; i < m; i++) vb[i] = b[m - 1 - i] - '0';

    // 结果数组,最多 n+m 位
    vector<int> result(n + m, 0);
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            result[i + j] += va[i] * vb[j];
        }
    }

    // 统一处理进位
    int carry = 0;
    for (int i = 0; i < n + m; i++) {
        int sum = result[i] + carry;
        result[i] = sum % 10;
        carry = sum / 10;
    }

    // 去掉前导零
    int len = n + m - 1;
    while (len > 0 && result[len] == 0) len--;

    string ans;
    for (int i = len; i >= 0; i--)
        ans += (char)('0' + result[i]);
    return ans;
}

int main() {
    string a, b;
    cin >> a >> b;
    cout << bigMul(a, b) << endl;
    return 0;
}

⚠️ 易错点:

  • 竖式乘法中 result[i+j] += va[i] * vb[j],先累加后进位,不要边乘边进位(容易出错)。
  • 结果数组大小开 n + m 位即可(两个 n、m 位数相乘最多 n+m 位)。
  • 高精度×低精度中,b 可能较大,用 long long 存中间乘积和进位防止溢出。

五、高精度除法(高精度 ÷ 低精度) ​

GESP 五级只要求掌握高精度除以低精度(高精度÷高精度不在五级考纲范围内)。

思路: 从高位到低位逐位试商,余数带入下一位:

       ____567___
   12 ) 6804
       60          ← 6÷12 不够,商0;68÷12=5余8
       ---
        80        ← 余数8带下一位0 → 80÷12=6余8
        72
        ---
         84      ← 余数8带下一位4 → 84÷12=7余0
         84
         --
          0
cpp
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

// 高精度 a 除以低精度 b,返回 {商, 余数}
pair<string, int> bigDivSmall(const string& a, int b) {
    vector<int> result;
    int remainder = 0;  // 余数
    for (int i = 0; i < (int)a.size(); i++) {
        int cur = remainder * 10 + (a[i] - '0');  // 余数×10 + 当前位
        result.push_back(cur / b);                 // 商
        remainder = cur % b;                       // 更新余数
    }

    // 去掉前导零(正序存储下,去掉开头的零)
    int start = 0;
    while (start < (int)result.size() - 1 && result[start] == 0)
        start++;

    string quotient;
    for (int i = start; i < (int)result.size(); i++)
        quotient += (char)('0' + result[i]);
    return {quotient, remainder};
}

int main() {
    string a;
    int b;
    cin >> a >> b;
    pair<string, int> result = bigDivSmall(a, b);
    cout << "商: " << result.first << endl;
    cout << "余数: " << result.second << endl;
    return 0;
}

⚠️ 易错点:

  • 除法是正序处理(从高位到低位),与加减乘的倒序不同,这是最容易搞混的地方!
  • 余数必须 ×10 后再带下一位,即 remainder * 10 + 当前位数字。
  • 商的前导零要去掉,但结果为 0 时保留一个 0。

🎯 考试常见考法:给出一段高精度除法代码,挖空 cur = remainder * 10 + (a[i] - '0') 这行,考查对试商过程的理解。


六、高精度四种运算对比 ​

运算存储方式处理方向核心操作特殊处理
加法倒序低位→高位逐位相加 + 进位循环后检查 carry
减法倒序低位→高位逐位相减 + 借位比较大小、去前导零
乘法(×低精度)倒序低位→高位逐位相乘 + 进位乘 0 特判
乘法(×高精度)倒序低位→高位i+j 位累加 + 统一进位结果开 n+m 位
除法(÷低精度)正序高位→低位余数×10+当前位试商去商的前导零

🔑 一句话记忆:加减乘倒着来,除法正着来;进位加、借位减、乘积累加、余数×10。


七、📝 实战练习 ​

练习题 1(单选题) ​

题目: 用高精度加法计算 99999999999999999999 + 1,结果是多少?

A. 100000000000000000000 B. 99999999999999999999 C. 10000000000000000000 D. 1000000000000000000000

🔑 点击查看答案与解析

答案: A

解析: 20 个 9 加 1 等于 1 后面跟 20 个 0,即 10^20 = 100000000000000000000,共 21 位。

用代码模拟:最低位 9+1=10,写 0 进 1;进位逐位传递,每一位都是 9+1=10,最终最高位产生进位 1,结果为 1 后跟 20 个 0。这道题考查的就是循环结束后 carry != 0 时追加进位的处理——如果漏掉这一步,结果会错误地变成 20 个 0。

💡 这道题如果用 long long 直接计算会溢出(long long 最大约 9.2×10¹⁸),这正是高精度存在的意义。


练习题 2(判断题) ​

题目: 高精度加法中,数字应该正序存储在数组中(最高位在下标 0 处),这样符合我们的阅读习惯。( )

🔑 点击查看答案与解析

答案: 错误 ❌

解析: 高精度运算应该倒序存储——个位存在下标 0 处。原因是:

  1. 加减乘的进位都是从低位(个位)向高位进行的
  2. 倒序存储时,进位可以直接 push_back 到数组末尾
  3. 如果正序存储,进位需要在数组头部插入,非常麻烦

🍎 生活比喻:就像列竖式计算——我们从个位开始算,一步一步往高位走。倒序存储就是让数组的"开头"对应竖式的"右边"(个位)。


练习题 3(单选题) ​

题目: 高精度减法 1000 - 1 的结果数组(倒序存储)是?( )

A. [9, 9, 9]
B. [0, 0, 0, 1]
C. [9, 9, 9, 0]
D. [1, 0, 0, 0]

🔑 点击查看答案与解析

答案: A

解析:

1000 - 1 = 999

倒序存储(个位在 0 位):

  • 个位:9 → 下标 0
  • 十位:9 → 下标 1
  • 百位:9 → 下标 2

结果数组是 [9, 9, 9],选 A。

🔥 关键考点:减法后要去前导零(这里对应的是数组末尾的零)。1000 - 1 计算过程中千位的 1 被借位后变成 0,最后要去掉,所以结果只有 3 位。


练习题 4(单选题) ​

题目: 高精度乘法中,一个 n 位的数乘以一个 m 位的数,结果最多有多少位?( )

A. n + m
B. n + m - 1
C. n × m
D. max(n, m)

🔑 点击查看答案与解析

答案: A

解析:

n 位最大值 = 10ⁿ - 1,m 位最大值 = 10ᵐ - 1 (10ⁿ - 1) × (10ᵐ - 1) < 10ⁿ × 10ᵐ = 10ⁿ⁺ᵐ

10ⁿ⁺ᵐ 是 1 后面跟 n+m 个 0,共 n+m+1 位,但乘积严格小于它,所以最多 n+m 位。

例如:99(2位)× 99(2位)= 9801(4位)= 2+2 位

选 A。

💡 最少位数是 n+m-1(比如 10×10=100,2+2-1=3 位)。


练习题 5(单选题) ​

题目: 关于高精度除法(高精度 ÷ 低精度),下列说法错误的是?( )

A. 除法从高位开始计算,和加减乘不同
B. 每一步的余数要乘以 10 加上下一位再试商
C. 除法也需要倒序存储
D. 商的前面可能有前导零需要去掉

🔑 点击查看答案与解析

答案: C

解析:

  • A 正确 ✅:加减乘从低位(个位)开始,除法从高位开始
  • B 正确 ✅:这是除法的核心步骤——余数×10+下一位
  • C 错误 ❌:除法从高位开始算,所以通常用正序存储(或用倒序但从后往前算)
  • D 正确 ✅:比如 1000 ÷ 5 = 0200,前面的 0 要去掉

💡 记忆口诀:加减乘倒着来,除法正着来。就像手工计算——加法从右往左加,除法从左往右除。


练习题 6(判断题) ​

题目: 高精度加法结果也需要去除前导零。( )

🔑 点击查看答案与解析

答案: 错误 ❌

解析: 加法是从低位往高位算,进位只会在最高位产生,不会出现前导零。前导零问题主要出现在减法(借位可能导致高位变零)和除法(商的高位可能是零)中。

⚠️ 注意:但如果两个数都是 0,加法结果也是 0,这不叫"前导零",而是正常的结果。


练习题 7(程序阅读题) ​

题目: 阅读高精度加法代码片段,循环结束后 carry = 1 时,应该执行什么操作?( )

cpp
vector<int> add(vector<int>& a, vector<int>& b) {
    vector<int> res;
    int carry = 0;
    for (int i = 0; i < a.size() || i < b.size(); i++) {
        if (i < a.size()) carry += a[i];
        if (i < b.size()) carry += b[i];
        res.push_back(carry % 10);
        carry /= 10;
    }
    // 循环结束后,carry = 1
    ____?____
    return res;
}

A. 什么都不用做
B. res.push_back(carry)
C. res.insert(res.begin(), carry)
D. carry = 0

🔑 点击查看答案与解析

答案: B

解析:

循环结束后如果 carry != 0,说明最高位还有进位,需要追加到结果的末尾(也就是最高位)。

因为是倒序存储,数组末尾对应数字的最高位,所以用 push_back 就可以了。

选 B。

🔥 高频考点:这是高精度加法最经典的代码填空题——循环结束后别忘了处理最后的进位!999 + 2 = 1001,如果漏掉这一步,结果就变成 001 了。


练习题 8(编程题) ​

题目: 请编写程序,计算 n 的阶乘(n!),其中 1 ≤ n ≤ 50。

由于结果可能很大,请使用高精度乘法(数组存储)。

输入:一个整数 n
输出:n! 的值

样例输入:10
样例输出:3628800

🔑 点击查看答案与解析

参考答案:

cpp
#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n;
    cin >> n;
    
    vector<int> res;
    res.push_back(1);  // 初始结果为 1
    
    for (int i = 2; i <= n; i++) {
        int carry = 0;
        for (int j = 0; j < res.size(); j++) {
            int product = res[j] * i + carry;
            res[j] = product % 10;
            carry = product / 10;
        }
        // 处理剩余进位
        while (carry != 0) {
            res.push_back(carry % 10);
            carry /= 10;
        }
    }
    
    // 倒序输出
    for (int i = res.size() - 1; i >= 0; i--) {
        cout << res[i];
    }
    cout << endl;
    
    return 0;
}

解析:

阶乘 = 1 × 2 × 3 × ... × n,这是高精度乘低精度的经典应用:

  1. 初始结果为 1(存在数组中,倒序存储)
  2. 从 2 乘到 n,每一步都是高精度数组 × 一个整数
  3. 乘完后处理进位(进位可能不止一位,用 while 循环)
  4. 最后倒序输出结果

时间复杂度:O(n × len),len 是当前结果的位数。

💡 50! 大约有 65 位,100! 有 158 位,早就超过 long long 了,必须用高精度。


八、📊 考点总结表 ​

考点重要程度出题形式易错点
倒序存储原理⭐⭐⭐⭐选择/判断为什么要倒序搞不清楚
高精度加法(进位)⭐⭐⭐⭐⭐编程题/代码填空漏掉最后的进位
高精度减法(借位)⭐⭐⭐⭐编程题忘记比较大小;前导零没去干净
减法的大小比较⭐⭐⭐⭐选择/编程不知道先比较大小再减
高精 × 低精⭐⭐⭐⭐编程题/代码填空进位是多位数,要用while
高精 × 高精⭐⭐⭐编程题结果数组位置 i+j 搞不清
高精 ÷ 低精⭐⭐⭐代码填空/程序阅读从高位开始算;余数×10+下一位
前导零处理⭐⭐⭐⭐判断/代码填空加法不需要,减法除法需要
结果位数估算⭐⭐⭐选择乘法最多 n+m 位
阶乘高精度实现⭐⭐⭐⭐编程题经典应用,必须掌握

⭐ 必背速记卡 ​

┌─────────────────────────────────────────────┐
│         高精度运算 · 必背速记卡             │
├─────────────────────────────────────────────┤
│  1. long long 装不下时,用数组模拟高精度    │
│  2. 加减乘:倒序存储,个位在下标 0          │
│  3. 除法:正序存储,从高位开始算            │
│  4. 加法:逐位相加,carry%10留,carry/=10进 │
│  5. 加法循环结束,carry!=0 要追加到末尾     │
│  6. 减法:先比大小,大减小,不够减就借位    │
│  7. 减法借位:高位减1,当前位加10           │
│  8. 减法结果要去前导零(数组末尾的零)      │
│  9. 乘法:n位 × m位,结果最多 n+m 位       │
│ 10. 高精×低精:每一位乘 + 进位              │
│ 11. 高精×高精:i位×j位 → 结果i+j位         │
│ 12. 除法:余数×10 + 下一位 → 试商          │
│ 13. 除法结果也要去前导零                    │
│ 14. 输出时倒序输出(从数组末尾往头输出)    │
│ 15. 阶乘是高精度乘法的经典应用              │
└─────────────────────────────────────────────┘

百炼成钢,融会贯通