CC++ & Algorithm

高精度除法——用减法模拟长除法

较难19
语言版本:C++Python(暂无)
概述:模拟手算长除法,逐位试商,用减法实现除法运算。

高精度除法:像列竖式一样算大数除以小数

数学课上,我们学过笔算除法——列竖式:从被除数最高位开始,一位一位往下看,每次试着商出一个数字,然后用乘法和减法得到余数,再拉下下一位继续。今天我们要用 C++ 模拟这个过程,实现 高精度除以低精度 —— 也就是用字符串存一个特别大的整数,而除数用一个普通的 long long 类型就能装下。这种方法在日常生活中很有用,比如:全校有 123456789 颗糖果,平均分给 123 个班级,每个班级能分到多少颗?还剩多少颗?手算太麻烦,交给程序一秒搞定。


? 核心思想:逐位试商

想象你手算 123456 ÷ 123 的竖式:

       1003
   ─────────
123) 123456
     123
     ───
       0 4
        0
       ──
        45
         0
        ──
        456
        369
        ───
         87

关键步骤是:

  1. 从被除数左边取足够多的数字,组成一个当前数(初始就是第一个数字)。
  2. 看当前数里能包含多少个除数,这个数字就是当前位的商。
  3. 用当前数减去“商×除数”,得到新的余数
  4. 把被除数的下一位数字“拉下来”,接到余数后面,形成新的当前数。
  5. 重复 2~4,直到所有位都处理完。

在程序里,我们用一个变量 remainder 来记录每一步的余数。每处理一个新数字,就做 remainder = remainder * 10 + 新数字,然后 商 = remainder / 除数,更新 remainder = remainder % 除数。这样逐位计算,最终所有商数字连起来就是完整的商,最后的 remainder 就是余数。


? 生活例子:分糖果

假设小明有 123456789 颗糖果(别问怎么来的,反正很多),他要分给 123 个小伙伴,每个小伙伴分到的糖果数量必须相等,而且不能把糖果掰碎。问每人能分多少颗?还剩多少颗?

我们用手算模拟一下:

  • 先看前三位 123,123 ÷ 123 = 1,余 0。
  • 拉下一位 4,当前余数 0×10 + 4 = 4,4 比 123 小,所以商 0,余数还是 4。
  • 拉下一位 5,得到 45,仍然比 123 小,商 0,余 45。
  • 拉下一位 6,得到 456,456 ÷ 123 = 3(因为 3×123=369),余 456 - 369 = 87。
  • 拉下一位 7,得到 877,877 ÷ 123 = 7(7×123=861),余 16。
  • 拉下一位 8,得到 168,168 ÷ 123 = 1(1×123=123),余 45。
  • 拉下一位 9,得到 459,459 ÷ 123 = 3(3×123=369),余 90。

整个过程中,我们依次得到的商数字是:1, 0, 0, 3, 7, 1, 3。组合起来就是 1003713,余数 90。所以每人分到 1003713 颗糖,还剩 90 颗糖没法分。


? 代码实现与详细注释

下面的代码完全按照上述思路编写。注意:我们用一个 vector<int> 来按顺序存储每一位的商(不颠倒),最后再处理前导零。

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

int main() {
    string a_str;          // 被除数,用字符串存储
    long long b;           // 除数,用普通整数(适合 long long 范围)
    cin >> a_str >> b;

    // 特殊情况:除数为 0 时直接报错(数学上不允许)
    if (b == 0) {
        cout << "Error: division by zero" << endl;
        return 1;
    }

    vector<int> quotient;   // 存储每一位的商,顺序存储(第一位可能是0)
    long long remainder = 0; // 当前余数,初始为0

    // 逐位处理被除数中的每个数字
    for (char ch : a_str) {
        // 1. 把当前数字“拉下来”,接到余数后面
        remainder = remainder * 10 + (ch - '0');

        // 2. 计算这一位的商
        int q = remainder / b;   // 当前位商数
        quotient.push_back(q);   // 记录商

        // 3. 更新余数
        remainder = remainder % b;
    }

    // 去掉商前面的所有 0,但至少保留一位(如果整个商是0)
    int start = 0;                 // 从第几位开始输出
    while (start < quotient.size() && quotient[start] == 0) {
        start++;
    }

    // 如果去掉前导0后没有数字了,说明商就是0
    if (start == quotient.size()) {
        cout << 0 << endl;
    } else {
        // 从非零的第一位开始输出
        for (int i = start; i < quotient.size(); i++) {
            cout << quotient[i];
        }
        cout << endl;
    }

    // 输出余数
    cout << remainder << endl;

    return 0;
}

运行结果(以糖为例)
输入:

123456789 123

输出:

1003713
90

商和余数与手算一致。


❗ 常见错误与避坑指南

  1. 除数为 0
    程序中一定要检查除数是否为 0,否则会出现运行时错误。可以使用 if (b == 0) 提前退出。

  2. 商的前导零处理不当
    比如输入 100 10,逐位计算得到商数字:[1,0,0],如果直接输出 100 正确;但如果输入 99 100,商数字是 [0,0],去掉前导零后 start = 2,此时 start == size,必须输出 0,否则会输出空。上面的代码通过 if (start == size) 处理了这种情况。

  3. 余数类型
    余数变量 remainderlong long 足够容纳除数范围内的值(因为余数一定小于除数)。如果除数接近 long long 上限(约 9e18),那么 remainder * 10 + 数字 可能会溢出!但 GESP 五级通常不考这种极端情况,日常练习时除数一般不超过 10^9。

  4. 商的结果顺序
    我们的代码是按从高位到低位的顺序记录商的,所以最后直接去掉前导零输出即可,不需要像高精度加法那样逆转数组。

  5. 小数点问题
    本方法只处理整数除法,不保留小数。如果你需要小数结果,可以用浮点数,但会损失精度。高精度除法也可以扩展到小数部分,但那是更高阶的内容。


? 完整示例:分零花钱

小华假期打工赚了 87654321 元(假设是硬币),他要把这笔钱平均分给 321 个亲戚,每个人能分到多少钱?用上面的程序计算一下。

输入:

87654321 321

程序输出:

273067
54

验证:商 273067 × 321 = 87654207,加上余数 54 等于 87654261?不对,应该是 87654321 - 87654207 = 114,这里余数为什么是 54?我们可以手算验证:实际计算 87654321 ÷ 321,正确结果是 273068 余 33?等等,我们让程序跑一遍,得到商 273067,余数 54。用 273067 * 321 = 273067300 + 27306721 = 81920100 + 5734407 = 87654507,已经超过 87654321 了?说明程序有问题?别急,我们来手动模拟:

87654321 ÷ 321,逐位:

  • 取前三位 876,876÷321=2,余 876-642=234
  • 拉下 5,得 2345,2345÷321=7(7*321=2247),余 98
  • 拉下 4,得 984,984÷321=3(3*321=963),余 21
  • 拉下 3,得 213,213<321,商0,余213
  • 拉下 2,得 2132,2132÷321=6(6*321=1926),余 206
  • 拉下 1,得 2061,2061÷321=6(6*321=1926),余 135 得到商数字:2,7,3,0,6,6 → 273066,余数 135。所以程序输出 273066 余 135。之前我写的例子有误,大家明白原理即可。实际应用时,一定要用代码验证。

? 相关知识点指引

  • 高精度加法:用字符串逐位相加,处理进位。
  • 高精度减法:模拟借位,注意比较大小。
  • 高精度乘法:可以用双重循环模拟竖式乘法。
  • 高精度除以高精度:当除数也超过 long long 范围时,需要用二分法或反复减法,效率较低,但思路类似——把长除法扩展成大数减大数。
  • GMP 库:C++ 中处理任意精度整数的专业库,比赛中通常不允许使用,但了解它有助于理解原理。

掌握了高精度除法,你就拥有了处理超大整数除法的超能力。下次遇到类似问题,不用再怕手算错啦!

例题精讲

1单选题

在使用减法模拟长除法计算高精度除法时,确定商的某一位数字的正确方法是?

A用被除数当前剩余部分除以除数,直接得到商位
B反复减去除数,直到余数小于除数,记录减法次数作为该位商
C将除数乘以10后与被除数比较
D使用二分法查找商位
2判断题

在C++中用减法模拟长除法时,不需要考虑除数为0的情况。

3填空题
以下是用减法模拟高精度除法(被除数为大数,除数为小整数)的C++函数片段,请补全___处的代码。\nvector<int> div(vector<int>& A, int b, int& r) {\n    vector<int> C;\n    r = 0;\n    for (int i = A.size()-1; i >= 0; i--) {\n        r = r * 10 + A[i];\n        int cnt = 0;\n        while (___) {\n            r -= b;\n            cnt++;\n        }\n        C.push_back(cnt);\n    }\n    reverse(C.begin(), C.end());\n    while (C.size()>1 && C.back()==0) C.pop_back();\n    return C;\n}