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

1.11 高精度乘法

乘法不再是简单的逐位对齐相加,而是要弄清楚"两个数各自哪一位相乘,结果该落到哪一个位置",这是本节的核心。

本页目录
① 核心思路:位置对应相乘

加减法都是"同一位置对齐运算"——个位对个位、十位对十位。但乘法完全不同:数组下标为 i 的位和下标为 j 的位相乘,结果不是放在第 i 位或第 j 位,而是要放到下标 i + j 的位置。

这背后的道理其实和小学数学是一致的:数组下标 i 存的是"10 的 i 次方"这一位,两个"10 的幂"相乘,指数是相加的(10ⁱ × 10ʲ = 10^(i+j)),所以乘积自然就落在了第 i+j 位。

23 × 45 中,每一位相乘落在哪个位置
b[0]=5 b[1]=4
a[0]=3 15
→ 位置 0+0=0
12
→ 位置 0+1=1
a[1]=2 10
→ 位置 1+0=1
8
→ 位置 1+1=2
23 倒序存储是 a=[3,2](a[0]=个位3,a[1]=十位2),45 倒序存储是 b=[5,4]。四对数字两两相乘,乘积按"下标相加"的规则分别落到结果的第 0、1、1、2 位——注意有两个乘积(12 和 10)都落在了位置 1,说明同一个位置可能会收到好几份贡献,需要全部累加起来。
② 统一处理进位

把上面 4 个乘积按位置分组累加,会得到一个"每一位还没规整"的中间结果——某些位置的数字可能远大于 9(因为多个乘积叠在了一起),需要像加法一样再做一次进位处理,把每一位规整回 0~9 的范围。

8
8
位置 2
12
10
22
位置 1
15
15
位置 0
把 [15, 22, 8] 从个位开始统一进位
位置 0 是 15,留个位 5,进 1 到位置 1;位置 1 原本是 22,加上进位 1 变成 23,留 3,进 2 到位置 2;位置 2 原本是 8,加上进位 2 变成 10,留 0,再进 1 到位置 3。最终从高到低读出 1 0 3 5,正好是 23 × 45 = 1035
📖
和加减法的进位有什么不同?加法的进位是"边算边处理"——算完一位立刻处理进位再算下一位;乘法则是先把所有位置该收到的乘积都累加完,最后再统一从低位到高位扫一遍处理进位。这是因为乘法某个位置可能同时收到好几个乘积的贡献(如上面的位置 1),提前进位反而会打乱后续乘积的累加,所以要等全部乘完、加完之后再统一处理。
③ 完整代码:高精度 × 高精度
C++ · 高精度乘法
1string s1, s2;
2cin >> s1 >> s2;
3
4int len1 = s1.size(), len2 = s2.size();
5vector<int> a(len1, 0), b(len2, 0);
6
7for (int i = 0; i < len1; i++)
8{
9 a[i] = s1[len1 - 1 - i] - '0';
10}
11for (int i = 0; i < len2; i++)
12{
13 b[i] = s2[len2 - 1 - i] - '0';
14}
15
16vector<int> c(len1 + len2, 0); // 结果最多 len1+len2 位
17for (int i = 0; i < len1; i++)
18{
19 for (int j = 0; j < len2; j++)
20 {
21 c[i + j] += a[i] * b[j]; // 核心:乘积累加到第 i+j 位
22 }
23}
24
25// 统一处理进位:从低位到高位扫一遍
26for (int i = 0; i < (int)c.size() - 1; i++)
27{
28 c[i + 1] += c[i] / 10; // 本位超出 9 的部分进给高一位
29 c[i] %= 10; // 本位只留个位数字
30}
31
32// 去掉结果高位多余的 0
33int len = (int)c.size();
34while (len > 1 && c[len - 1] == 0)
35{
36 len--;
37}
38
39for (int i = len - 1; i >= 0; i--)
40{
41 cout << c[i];
42}
⚠️
时间复杂度:双层循环让高精度乘法的时间复杂度是 O(len1 × len2),比加减法的 O(len) 高出一个数量级。如果两个数都有几千位,这种"暴力枚举每一对下标"的写法可能会超时,需要更高级的算法(如 FFT 快速傅里叶变换)来优化,不过那已经超出了本节的范围。
④ 简化版:高精度 × 低精度

实际做题时,更常见的场景是"一个高精度大数,乘以一个 long long 范围内的普通整数"(比如求阶乘 n!,本质就是把一个不断变大的高精度数反复乘以一个小整数)。这种情况不需要双层循环,思路和加法几乎一样简单:

C++ · 高精度 × 低精度(例如阶乘)
1// 计算 20! (20 的阶乘),结果远超 long long 范围
2vector<int> c(1, 1); // 初始值为 1(只有一位,个位是 1)
3
4for (int k = 1; k <= 20; k++) // 依次乘以 1, 2, 3, ..., 20
5{
6 int carry = 0;
7 for (int i = 0; i < (int)c.size(); i++)
8 {
9 int cur = c[i] * k + carry; // 本位数字直接乘以 k,再加上进位
10 c[i] = cur % 10;
11 carry = cur / 10;
12 }
13 while (carry) // 进位可能不止一位(k 本身可以很大),要循环处理完
14 {
15 c.push_back(carry % 10);
16 carry /= 10;
17 }
18}
19
20for (int i = (int)c.size() - 1; i >= 0; i--)
21{
22 cout << c[i];
23}
🏆
为什么这个版本更简单?因为 k 是一个普通整数,不需要拆成数组,只要用高精度数组的每一位分别乘以这个整数、加上进位即可——不需要"位置相加"的双层循环,也不用等到最后才统一处理进位(这里的进位是"边乘边处理",和加法的进位方式一致)。这个写法在求阶乘、求 2 的 n 次方等题目中非常常用。
⑤ 常见陷阱

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

1
结果数组开小了:两个数相乘,结果的位数最多是 len1 + len2 位(不会更多),但也不会自动变小,声明结果 vector 时必须留出这么多空间,否则 c[i+j] 会访问越界。
2
过早处理进位:如果在双层循环内部就尝试处理进位,会因为同一位置后续还会收到新的乘积贡献而把已经进位的结果搅乱。正确做法是先把所有乘积按位置累加完毕,再统一从低位到高位扫一遍处理进位。
3
高精度乘低精度时忘记处理连续进位:低精度乘数 k 如果比较大(比如 k=1000),单次的 carry 可能不止一位数字,简化版代码里必须用 while(carry) 循环把进位完全展开成多位再退出,不能只处理一次就了事。
🏆
接下来:高精度除法(1.12 节)同样有"高精度 ÷ 高精度"和"高精度 ÷ 低精度"两个版本,其中低精度版本更常用、也更简单——模拟竖式除法,从最高位开始,带着余数往下走。