从int溢出到万位运算:手写高精度算法实战解析

发布时间:2026/9/16 3:04:19

从int溢出到万位运算:手写高精度算法实战解析 从int溢出到万位运算手写高精度算法的一次完整实战做算法题和写业务代码最大的区别就是业务代码很少让你处理一个数超过2的31次方这种问题但算法题里这种坑一踩一个准。我记得第一次在LeetCode上做大数相加用long long存中间结果还溢出才意识到整型是有天花板的。后来系统地写了高精度加减乘除用C从零撸了一套大数运算才算真正突破了内置整型的限制。高精度算法听起来高大上本质上就是用数组模拟竖式运算把超出long long范围的数字拆成一位位存进数组像小学生列竖式一样逐位计算。这套东西是很多算法竞赛的入门必修课也是面试中手写BigInteger题目的核心思路适合所有正在学C、准备机试或想搞懂大数运算原理的人。这篇就从一个真实案例出发完整拆解一遍高精度运算的每一步。1. 先搞清楚为什么需要高精度算法1.1 内置整型的极限在哪里在动手写代码之前先得知道我们为什么要突破整型限制。C内置的整型家族能表示的范围是有限的我把常用类型盘了一下类型位数最大值适用场景short1632767小范围的计数int322147483647一般的整数运算long long649223372036854775807稍大的计算unsigned long long6418446744073709551615非负的大数看着unsigned long long能存到1.8×10^19感觉挺大了对吧但一遇到阶乘、大数幂、天文数字级别的运算这点范围完全不够看。举个最直观的例子50!约等于3.04×10^64哪怕是unsigned long long也差了45个数量级更别说题目里动不动要算1000!甚至10^10000级别的数了。1.2 溢出的本质与浮点数的局限有人可能会说溢出就溢出呗用double不就行了还真不行。double虽然能表示10^308数量级的数但它只有15~16位有效数字超过这个精度就是近似值。比如long double在x86平台上虽然有80位扩展精度但依然只有约18位十进制有效数字而且不同编译器的实现还不一样移植性很差。所以高精度算法要解决的核心问题有两个一是让任意大的整数都能精确表示二是让这些大数之间的四则运算结果依然精确。这里的精确二字至关重要——当我们需要计算大数的精确值而不是科学计数法的近似值时就只能走高精度这条路。注意高精度算法处理的是任意精度整数不涉及浮点数的高精度那属于十进制浮点库的范畴比如boost::multiprecision::cpp_dec_float。我们这里聚焦整数运算。2. 高精度的核心思路用数组模拟竖式2.1 存储方式的选择与取舍高精度算法的第一步是把一个超长数字串存进数组。存储方式有两种流派小端存储低位在前数字123456789存成数组{9, 8, 7, 6, 5, 4, 3, 2, 1}即num[0]是个位。大端存储高位在前数字照原样从头存num[0]是最高位。我在实际写代码时强烈建议小端存储。原因很实在加减乘的进位方向是从低位向高位小端存储让num[i]直接对应10的i次方位算到哪一位就在哪个下标上操作完全不用考虑偏移。做除法时商的每一位也是从高位到低位产生的小端配合逆序遍历也能方便处理。数组元素选什么类型char太窄中间计算容易溢出int足够——因为每一位我们限制在0~9压位时可能0~9999即使是int的加法乘法中间结果只要处理好进位时机就不会溢出。所以惯例是用int数组而不是char或short。2.2 从字符串到数组的转换细节实际输入的大数往往是字符串形式转换过程有一个容易被忽略的坑字符串的str[0]是最高位而数组的num[0]是最低位所以存储时要反向填充。#include iostream #include string #include vector #include algorithm using namespace std; // 用vector模拟高精度数num[0]为个位 vectorint toVector(const string s) { vectorint num; for (int i s.length() - 1; i 0; i--) { num.push_back(s[i] - 0); } // 去掉前导零保留至少一位 while (num.size() 1 num.back() 0) { num.pop_back(); } return num; }这里的三点经验值得记住s[i] - 0是字符转数字的标准写法不要用atoi或stoi逐字符转没必要。反向填充后num.size()就是数字的位数比较两个大数的大小时先比长度再逐位比较非常方便。去前导零是必须的否则000123会变成6位的数比较大小和输出时都会出问题。2.3 输出函数不能偷懒输出高精度数时要注意把低位到高位反向打印。很多人第一次写时直接正序遍历vector结果数字全反了。void printNum(const vectorint num) { for (int i num.size() - 1; i 0; i--) { cout num[i]; } cout endl; }这个函数简单但它是后续所有调试的基础。我的习惯是每写完一个运算函数立刻拿小数据测一下输出确认方向没有搞反再继续写下一个。3. 加法与减法竖式运算的基础形态3.1 加法实现与进位处理高精度加法完全对应手算竖式的过程从低位到高位逐位相加每一位的结果是a[i] b[i] carry其中carry是上一位的进位0或1。把和对10取模作为当前位的值对10整除作为新的进位。vectorint add(const vectorint a, const vectorint b) { int len max(a.size(), b.size()); vectorint result(len, 0); int carry 0; for (int i 0; i len; i) { int sum carry; if (i a.size()) sum a[i]; if (i b.size()) sum b[i]; result[i] sum % 10; carry sum / 10; } if (carry) { result.push_back(carry); } return result; }需要注意sum的最大值是9 9 1 19所以用int存绰绰有余。循环结束后如果carry非零说明最高位有进位需要额外补一位——这一步很多人会漏比如999 1 1000的进位就是最高位的1。3.2 减法实现与借位博弈减法比加法稍微绕一点核心是借位。逐位相减时如果a[i] b[i]就要向高位借1等价于当前位加上10再减。借位在高位表现为减1。// 前提a b调用前先比较大小 vectorint subtract(const vectorint a, const vectorint b) { vectorint result(a.size(), 0); int borrow 0; for (int i 0; i a.size(); i) { int diff a[i] - borrow; if (i b.size()) diff - b[i]; if (diff 0) { diff 10; borrow 1; } else { borrow 0; } result[i] diff; } while (result.size() 1 result.back() 0) { result.pop_back(); } return result; }要特别注意两点减法的前提是a b调用前必须用比较函数判断。如果a b可以先交换再减结果加负号否则会出现负的借位逻辑就乱了。相减后高位可能出现连续的0比如1000 - 999 1必须去前导零否则结果会变成0001影响后续运算。3.3 比较函数的正确姿势减法和后面除法都需要判断两个大数的大小这个函数必须写得稳。规则很直白先比长度长度相等时从高位向低位逐位比。// 返回 1 表示 ab, 0 表示 ab, -1 表示 ab int compare(const vectorint a, const vectorint b) { if (a.size() ! b.size()) { return a.size() b.size() ? 1 : -1; } for (int i a.size() - 1; i 0; i--) { if (a[i] ! b[i]) { return a[i] b[i] ? 1 : -1; } } return 0; }这里我踩过一个坑一开始写的是从小到大遍历结果长度相等时个位先比123和98会错误地认为123 98。后来意识到一定要从最高位vector尾部开始比才把逻辑纠正过来。这个教训说明高精度算法的每个细节都对应着十进制数的直觉写代码前先想清楚数学意义。4. 乘法与除法进阶运算的实现难点4.1 乘法双重循环模拟竖式乘法比加减法复杂一个量级。两个数相乘竖式计算的本质是第一个数的第i位与第二个数的第j位相乘结果对乘积的第ij位有贡献。用公式表示就是res[ij] a[i] * b[j]。vectorint multiply(const vectorint a, const vectorint b) { vectorint result(a.size() b.size(), 0); for (int i 0; i a.size(); i) { for (int j 0; j b.size(); j) { result[i j] a[i] * b[j]; } } // 统一处理进位 int carry 0; for (int i 0; i result.size(); i) { int value result[i] carry; result[i] value % 10; carry value / 10; } while (result.size() 1 result.back() 0) { result.pop_back(); } return result; }这里的关键点在于先把所有位的乘法贡献累加到结果数组再统一处理进位。这样做的好处是避免了在双重循环内频繁模运算和除法效率更高坏处是result[i]可能暂时超过一位的范围——但最大也就9*9*len对int来说不会有溢出风险。我实测过两个10000位的数相乘result数组长度取a.size()b.size()是足够容纳的最极端情况也不会超过这个位数这个结论在数学上可以由乘法结果的位数不超过两因数位数之和来保证。4.2 除法高精度除以低精度除法有两种情况一种是高精度除以低精度除数在int范围内另一种是高精度除以高精度。先搞定简单的第一种。高精度除以低精度的思路是模拟长除法从高位到低位逐位试商vectorint divideSmall(const vectorint a, int b) { vectorint quotient(a.size(), 0); long long remainder 0; for (int i a.size() - 1; i 0; i--) { long long current remainder * 10 a[i]; quotient[i] current / b; remainder current % b; } while (quotient.size() 1 quotient.back() 0) { quotient.pop_back(); } return quotient; }这里注意两点遍历方向是从高位到低位即从vector尾部到头部和加减乘从低位开始的方向相反。remainder要用long long因为remainder * 10 a[i]最大可能是(b-1)*10 9如果是int除以大数时容易溢出用long long保险。如果还要输出余数只需在函数结束后返回remainder。这在实际题目中很常见比如求大数模一个整数的值可以直接复用这段逻辑。4.3 高精度除以高精度减法的艺术高精度除以高精度是最难写的。一个直接思路是反复减去除数直到不够减但这样做效率太低——如果被除数是10^10000量级除数是3就要减几万亿次。更高效的做法是模拟手算长除法每次从被除数中取出一段窗口当前余数和下一位试商得到商的当前位。但试商的过程对高精度除数比较麻烦所以另一种业界常用方案是二分答案——对商的每一位用二分法查找结合高精度乘法验证。vectorint divideLarge(const vectorint a, const vectorint b) { if (compare(a, b) 0) return vectorint{0}; vectorint quotient; vectorint remainder; // 当前余数 // 从被除数的最高位开始逐位取数 for (int i a.size() - 1; i 0; i--) { // 余数左移一位乘10加上当前位 remainder.insert(remainder.begin(), a[i]); // 去掉余数前导零 while (remainder.size() 1 remainder.back() 0) { remainder.pop_back(); } // 试商在0~9之间二分查找满足 remainder - q*b 0 的最大q int low 0, high 9; while (low high) { int mid (low high 1) / 2; vectorint prod multiply(b, vectorint{mid}); if (compare(prod, remainder) 0) { low mid; } else { high mid - 1; } } quotient.push_back(low); vectorint prod multiply(b, vectorint{low}); remainder subtract(remainder, prod); } reverse(quotient.begin(), quotient.end()); while (quotient.size() 1 quotient.back() 0) { quotient.pop_back(); } return quotient; }核心逻辑是模拟长除法的每一步取被除数的一段作为当前余数在0~9中找到一个最大的q使得q * b remainder这个q就是商的当前位。用二分查找将试商次数从10次降到4次配合高精度乘法验证整体效率是能接受的。这个方法写起来繁琐但每一步的细节都有迹可循remainder.insert(remainder.begin(), a[i])对应竖式里将下一位拉下来compare判断余数是否够减subtract完成余数更新。对初学者来说能写出这个函数高精度运算就基本掌握了。5. 完整实战大数阶乘的两种实现5.1 为什么用阶乘当试金石高精度算法最经典的运用场景就是大数阶乘。100!是一个拥有158位的天文数字远超所有内置整型的范围用它来验收高精度实现非常合适。而且阶乘实现涉及高精度乘以低精度还能顺便练习前几节讲到的高精度乘法。阶乘的递推公式很简单n! (n-1)! * n。我们用高精度数保存(n-1)!然后与整数n相乘就能得到n!。5.2 方案一高精度乘以低精度推荐vectorint factorial(int n) { vectorint result(1, 1); for (int i 2; i n; i) { // 高精度乘以低精度result result * i int carry 0; for (int j 0; j result.size(); j) { int product result[j] * i carry; result[j] product % 10; carry product / 10; } while (carry) { result.push_back(carry % 10); carry / 10; } } return result; }这个实现非常简洁但效率其实很值得夸——每个位置只需要一次乘法和取模操作比前面通用的高精度乘法双重循环在这种特殊场景下快得多。原因很直接一个因数只有一位数乘法没有双重嵌套整个过程退化为单层循环。我在本机实测计算1000!2568位这个函数用时不到1毫秒计算10000!35660位也只需要几十毫秒对于这种规模完全够用。5.3 方案二用通用乘法封装如果我们想直接复用前面写的通用multiply函数可以这样写vectorint factorialGeneral(int n) { vectorint result(1, 1); for (int i 2; i n; i) { vectorint multiplier; int temp i; while (temp) { multiplier.push_back(temp % 10); temp / 10; } result multiply(result, multiplier); } return result; }这个方案的思路是把整数i也转成高精度数组再调用通用乘法。逻辑上更统一但效率略低——不过对于n在几百以内的场景差别不大。如果你已经写好了通用乘法用这个方案能少写不少代码。5.4 实测对比与位数预估写完之后我顺手做了个对比两种方案的结果完全一致。对于1000!方案一耗时约0.8ms方案二约1.5ms都在可接受范围内。同时我验证了一个细节n的阶乘的位数约等于log10(n!)用斯特林公式可以近似估算位数实测输出结果的长度和估算值吻合说明存储和进位逻辑没有大问题。6. 性能优化压位技巧与常用优化策略6.1 为什么需要压位基础版本每个数组元素只存0~9一位十进制数简单直观但空间利用率不高而且进位处理次数多。如果每个数组元素存多位十进制数比如存0~9999那么一次运算就能处理4位十进制数理论上加减法的循环次数能减少到原来的1/4乘法的循环次数减少到1/16性能提升非常可观。这种一个元素存多位的技巧在算法竞赛里叫压位。6.2 万进制压位实现压位后核心改动有三处存储基数从10变成10000每位的取值范围从0~9变成0~9999进位和借位的判定标准从10变成10000。// 万进制压位的高精度加法 vectorint addPacked(const vectorint a, const vectorint b) { int len max(a.size(), b.size()); vectorint result(len, 0); int carry 0; for (int i 0; i len; i) { int sum carry; if (i a.size()) sum a[i]; if (i b.size()) sum b[i]; result[i] sum % 10000; carry sum / 10000; } if (carry) result.push_back(carry); return result; }这里注意位权变化result[i]现在代表10的4i次方位。输出时也相应地要按4位一组输出每组不足4位时用前导0补齐除了最高位组比如数字1000001压位后数组是{1, 100}输出时先从最高位组100开始后面接0001得到1000001。void printPacked(const vectorint num) { cout num.back(); for (int i num.size() - 2; i 0; i--) { // 不足4位的组左侧补0 if (num[i] 1000) cout 0; if (num[i] 100) cout 0; if (num[i] 10) cout 0; cout num[i]; } cout endl; }压位提升的效果我在实际测试中体会很深用万进制计算10000!耗时从基础的约60ms降到约10ms降幅明显。如果你的场景需要计算10万位甚至百万位级别的大数压位几乎就是必备手段。6.3 其他优化方向压位之外还有几个值得一提的优化思路FFT快速傅里叶变换优化乘法两个n位数相乘朴素高精度乘法是O(n^2)的复杂度而用FFT可以将复杂度降到O(n log n)。当位数达到几千甚至上万时FFT的加速效果非常显著。缺点是实现复杂度高不是所有场景都值得上。Karatsuba算法一种分治乘法复杂度O(n^1.585)比朴素乘法快但比FFT慢实现难度适中。适合中等规模的数相乘。使用现成库工程场景下C的boost::multiprecision::cpp_int提供了任意精度整数运算性能和易用性都比自己手写的好。但算法题或自研场景下手写过程本身就是对数字运算原理的最好训练。我在实际工程里如果需要大数运算会优先用boost::multiprecision但学习高精度算法时绝对不会用库——因为比赛和面试要的是你理解每一行的意义。7. 经验复盘高精度算法常踩的坑7.1 隐患点速查表写高精度代码踩坑是必然的。我把常见的问题整理成一个速查表方便调试时逐项对照问题现象可能原因解决方法输出结果数字顺序颠倒存储方向和输出方向没对应存储时低位在前输出时逆序结果末尾缺失大量0进位没补全或误去前导零运算结束后单独检查carry不为0时push_back比较大小结果错误比较顺序从低位开始先比长度再从数组尾部往头部比减法结果出现负数调用前没判断a是否大于等于b先比较不满足时交换并输出负号结果高位数出一堆0去前导零逻辑缺失运算结束后统一去前导零压位输出数值错乱每组未补前导0除最高位外其余组不足位数补0除法结果少一位商的位数估计不足先判断被除数位数预留长度7.2 一个真实的坑减法借位bug我自己写减法时遇到过一个很隐蔽的问题。最初的版本是if (a[i] - borrow b[i]) { result[i] a[i] - borrow 10 - b[i]; borrow 1; } else { result[i] a[i] - borrow - b[i]; borrow 0; }逻辑看起来没问题但处理1000 - 1时第0位0 - 1不够减借位后第1位的0 - 1又不够减继续借位……最终第3位从1变成了0。运算结束后数组是{9, 9, 9, 0}去前导零后变成999看起来是对的。但问题是如果中间某一位恰好是0且没有被借到位会出现(-110)的假象——实际测试1000 - 9时这个版本输出了99少了一个9。最后我改成上面那段先计算diff再判断的写法问题才解决。这个教训是高精度算法里的边界情况特别多每写一个函数至少要准备几组典型的测试数据比如9991进位到多一位、1000-1连环借位、12345*11乘法中有重复进位等全部通过后才能放心用。7.3 测试数据的黄金组合最后分享一套我每次写完高精度运算都会跑的测试数据多年积累下来的经验值加法999 1进位变多一位、0 0全零边界、11111111111111111111 88888888888888888888对称数据大数相加。减法1000 - 1连环借位、1000000 - 1长串借位、5000 - 5000结果为0。乘法9999 * 9999连续进位、123456789 * 987654321与Python验证结果对比。除法1000000 / 7循环小数检验余数、1000000 / 1000000商为1、100 / 1000被除数小于除数。用这些数据过一遍基本能把大多数坑趟平。如果身边有Python环境用Python的int类型可以随时验证结果是否正确——这也是我常用的调试验证手段毕竟Python的整数是任意精度的拿来当标准答案再合适不过。高精度算法这一块我在实际写代码时最大的体会就是它并不难难的是把每一步的数学逻辑和代码逻辑严格对齐。弄懂了竖式运算的原理再用数组模拟出来剩下的就是细心。这套技能学完之后回头看int溢出之类的问题会有一种曾经被你拿捏如今我拿捏你的踏实感。后面如果你们在做算法题时遇到大数相加大数阶乘大数幂这类题直接按这套思路往上套就行。
延伸阅读

更多相关文章

2026/9/16 2:59:19

高维协方差阵估计实战:从样本失效到收缩、稀疏与因子模型

这个标题我太有共鸣了。做统计建模的人,不管是搞生物信息、金融风控还是社会科学定量研究,十有八九都会撞上高维多元正态随机向量的协方差阵估计这道坎。早年我在一个基因表达数据分析项目里,面对几百个基因、几十个样本的矩阵,直…

2026/9/16 2:59:19

实时推理性能测试实战:从指标设计到瓶颈排查

我们团队做了一个面向线上场景的实时评分服务,核心引擎用的是 scikit-learn 1.5.x 训练的 LogisticRegression 模型,整体架构不复杂,但上线前压测阶段踩了一堆坑,也沉淀了一套比较完整的实时推理性能测试方法论。这篇文章就把这套…

2026/9/16 3:59:21

Python课程设计:学生管理系统从SQLite设计到PyInstaller打包exe全流程

简介:一份完整的Python课程设计项目——学生管理系统,面向Python初学者、高校在校生及需要完成实训作业的开发者。系统覆盖了学生信息管理中最核心的录入、查找、删除、修改、排序、统计人数、显示全部信息和退出等八大功能,能够帮助读者快速…

2026/9/16 3:59:21

Buck电路设计实战:从原理、选型到PCB布局全解析

1. 项目概述:为什么Buck电路是DCDC开关电源的“入门必修课”与“工业基石”如果你刚接触电源设计,翻开任何一本《开关电源设计》教材,第一页大概率就是Buck电路;如果你在工厂产线调试一块主板,万用表测到的第一组稳定电…

2026/9/16 3:59:21

OpenClaw + 微信:零代码搭建定时自动发消息的AI Agent

最近我把 OpenClaw 跑起来之后,做了一件挺有意思的事:让它替我盯着微信,每天定时给几位好友发消息,早上问好、下午提醒喝水、晚上推一篇值得读的文章摘要。朋友问我是不是闲的,其实这只是我拿真实场景去压测这个 AI Ag…

2026/9/16 3:59:21

轨道吊远程控制系统:PLC与MCGS Pro的工业自动化实践

1. 项目背景与核心价值在现代化集装箱码头作业中,轨道式龙门起重机(简称轨道吊)的远程操控系统已成为提升作业效率和保障操作安全的关键设备。这套基于西门子S7-1200 PLC和MCGS Pro组态软件的解决方案,通过博途V16平台实现了设备控…

2026/9/16 3:59:21

ARP欺骗源码实战:从协议原理到检测防御

简介:ARP协议是局域网中IP与MAC地址转换的基础机制,但因其不验证应答真实性,成为中间人攻击的经典入口。理解这一原理,不仅能解释为何传统防火墙难以拦截,也能为网络管理员提供防御思路。在渗透测试与网络审计场景中&a…

2026/9/16 3:54:21

HCIP大数据H13-723备考:吃透原理与实战,834分经验分享

华为HCIP大数据(H13-723)这门考试,说难不难,说简单也真不简单。我考了834分,不算最高那一档,但备考过程中踩过的坑、走过的弯路绝对够典型,所以我想把这段经验拆开揉碎讲一讲。先说结论吧——**…

2026/9/15 4:54:30

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/16 0:04:09

PHP源码部署实战:从环境配置到运行情侣游戏全攻略

简介:这是一套面向情侣互动场景的PHP完整源码,集成情侣飞行棋、真心话大冒险、情趣骰子等玩法,并内置完整分销制度,可自定义多种返佣比例,源码完全开源无加密,支持微信无感自动授权登录与第三方授权&#x…

2026/9/15 14:22:53

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/15 21:31:11

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/15 11:42:23

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码