资讯详情

高精度计算入门与immense数据模型精讲(六)

📅 2026/11/3 19:30:14 | 华诺云谱 👁 阅读
高精度计算入门与immense数据模型精讲(六)
跳转链接高精度计算入门与immense数据模型精讲一高精度计算入门与immense数据模型精讲二高精度计算入门与immense数据模型精讲三高精度计算入门与immense数据模型精讲四高精度计算入门与immense数据模型精讲五高精度计算入门与immense数据模型精讲七高精度计算入门与immense数据模型精讲八高精度计算入门与immense数据模型精讲九immense大实数数据库前言四则运算的收官之战逆向求解的硬核挑战前面我们依次攻克了加减乘三种运算加法是线性叠加乘法有分治优化它们都是正向运算逻辑直观进位传递方向单一。但除法不一样——它是乘法的逆运算不能靠简单的逐位累加完成需要「试商-验证-调整」的迭代过程是四则运算里逻辑最复杂、细节最多、也最容易出bug的模块。很多初学者写高精度写到除法就卡壳不知道从哪一位开始算、试商怎么试、余数怎么传递、小数位数怎么控制、小数点位置怎么定。但只要掌握了**长除法竖式除法**的通用框架再辅以合适的试商优化这些问题都会迎刃而解。immense采用的就是工业界最经典的「长除法 二分试商 预计算倍数」组合先把除数化为整数消除小数影响再从高位到低位逐位计算商每一位商通过二分法在09之间快速确定同时预计算除数的19倍来避免重复乘法兼顾了代码简洁性和运行效率。本篇依然严格遵循「通用原理先行代码实现对照」的教学节奏先把高精度整数除法、实数除法的通用方法论讲透从竖式手算逻辑到三种试商方案对比再到小数点处理与精度控制再对照immense的源码逐段拆解Division核心函数的完整流程讲清每一步的设计考量与实现技巧。一、通用原理高精度除法的核心方法论1.1 本质复刻手算长除法高精度除法的底层逻辑和小学学的竖式长除法完全一致。我们先通过一个简单例子回顾人手算整数除法的标准步骤以1234 ÷ 56为例从被除数的最高位开始取和除数位数相同的前几位作为初始余数估算当前位的商0~9之间满足 商×除数 ≤ 余数 (商1)×除数用余数减去 商×除数得到新的余数从被除数落下下一位拼在余数后面重复2~4步直到被除数所有位都处理完毕如果需要小数部分就在余数后面补零继续算直到达到指定精度这个过程的核心是逐位确定商每一位只和当前余数有关算完一位就把余数传递下去。这也是所有高精度整数除法的通用框架。教学要点除法和加减乘有一个关键区别——加减乘都是从低位往高位算除法是从高位往低位算。因为商的最高位权重最大必须先确定。这也是为什么除法的遍历方向和其他三种运算相反。1.2 试商除法的核心难点手算的时候我们靠经验估商计算机必须有确定的方法找到正确的商位。常见的试商方案有三种各有优劣方案一朴素递减法从9开始往下逐个尝试找到最大的k使得 k×除数 ≤ 余数。优点实现极其简单几乎不会写错缺点最坏情况要试9次每次都要做一次乘法和比较效率最低方案二高位估算法用余数的前23位除以除数的前2位估算出商的近似值再上下调整12次验证。优点通常1~2次就能找到正确商速度最快缺点实现复杂边界情况多容易估大或估小需要额外修正逻辑方案三二分试商法在0~9的区间里做二分查找找到最大的k满足 k×除数 ≤ 余数。优点最多只需要4次比较log₂9≈3.17次数固定逻辑清晰边界少缺点需要多次比较运算但比较比乘法轻量得多适用场景比较运算实现完善的库二分试商是简洁又高效的选择immense采用的就是二分试商法并且额外做了预计算优化提前算好除数的1~9倍存在数组里二分的时候只需要比较大小不用临时计算乘法进一步提升了效率。1.3 整数长除法的通用算法步骤我们用更严谨的方式整理出无符号整数长除法的标准流程前提约定被除数a、除数b均为非负整数b≠0采用倒序存储高位在字符串末尾算法步骤初始化余数z初始为0商初始为空从最高位到最低位遍历被除数的每一位a. 把当前位数字落到余数的最低位等价于 余数×10 当前位数字b. 若余数 除数当前位商为0直接进入下一轮c. 否则通过试商找到最大的k∈[0,9]使得 k×b ≤ zd. k追加到商的对应位置e. z z - k×b得到新的余数清理商的前导零保证整数部分规范如果需要计算小数部分a. 循环指定的保留位数次i. 余数补零等价于余数×10ii. 试商得到当前位小数kiii. k追加到小数部分末尾iv. z z - k×b返回最终的商时间复杂度设除数位数为m商的总位数为p整数位小数位。每一位商需要O(log10)次比较每次比较是O(m)因此整体时间复杂度为O(p × m)和朴素乘法的O(n×m)处于同一量级。1.4 实数除法统一转化为整数除法和乘法一样实数除法也可以通过小数点移位完全转化为整数除法来处理。核心原则是先把除数化为纯整数。通用处理步骤统计除数的小数位数 d₂除数和被除数同时乘以 10^d₂小数点均右移d₂位此时除数变为纯整数统计被除数此时剩余的小数位数 d₁被除数再乘以 10^d₁也变为纯整数同时记录总偏移量 d₁按整数长除法计算小数部分保留指定精度最后把商的小数点左移 d₁ 位得到最终实数结果直观示例12.34 ÷ 5.6除数有1位小数两者同时乘10 →123.4 ÷ 56除数变为整数被除数此时有1位小数记偏移量 d₁1被除数再乘10 →1234 ÷ 56按整数长除法算得商≈22.0357商的小数点左移1位 →2.20357即为最终结果这样处理的最大好处是核心除法逻辑只需要处理整数除数不用考虑小数点对齐代码复用性极高边界也更少。1.5 精度控制除法必须有截断加法、减法、乘法的结果位数都是有限的加减最多差一位乘法位数是两者之和。但除法不一样——大量除法是除不尽的比如 1÷3 0.3333… 是无限循环小数。因此实数高精度除法必须设置精度上限指定保留多少位小数算到就停止。这是除法和其他四则运算最本质的区别它的结果长度不是由输入决定的而是由精度参数决定的。工业界的通用做法是设置一个全局小数保留参数用户可以按需调整。immense里对应的就是DRsvD全局变量C11环境下默认保留100位小数。1.6 符号与除零处理和乘法完全一致除法也遵循「符号与数值分离」的原则先取两个数的绝对值做无符号除法符号规则同号得正异号得负除零是必须严格处理的错误情况除数为零时应当抛出异常或触发断言不能让程序继续运行否则会出现无限循环或者未定义行为。1.7 常见坑点盘点初学者实现高精度除法最容易踩这几个经典坑遍历方向搞反除法要从被除数最高位开始倒序存储下最高位在字符串末尾必须从后往前遍历习惯性从下标0开始会从低位算起结果完全错误余数落位逻辑错误落下一位等价于 余数×10 当前位权重关系不能错小数部分补零也是同理每次补零就是余数×10试商越界二分边界处理不当可能出现商≥10的情况必须保证商严格在0~9之间小数点位置算错实数转整数的偏移量统计错误最终结果差10倍甚至更多除零未检查除数为零时进入死循环或者输出乱码结果二、immense 实现对照除法体系逐函数精讲immense的除法体系同样采用经典的三层架构核心运算层Division函数实现无符号实数长除法 二分试商符号调度层Centre_D函数处理除零检查与符号回填上层接口层/、/运算符重载我们从最核心的Division函数讲起这也是整个四则运算中最长、细节最多的函数。2.1 Division 函数无符号实数长除法我们把完整实现拆成五个阶段对应通用原理的每一步。阶段一除数整数化 偏移量记录immense Division(immense x, immense y) { while(!y.DCMp.empty()) y.Product_10(true),x.Product_10(true); int RDCM0; while(!x.DCMp.empty()){ x.Product_10(true); RDCM; } if(y.LTGp0)throw std::domain_error(Division by zero);对应通用原理的「实数转整数」步骤先把除数的小数全部右移到整数部分被除数同步移位保证除数先变成纯整数再把被除数剩余的小数也全部移到整数部分同时用RDCM记录移位次数——这就是后面商要左移的小数点位数除零检查除数为零直接抛出domain_error异常这一步完成后x和y都变为纯整数后续核心逻辑完全按整数长除法处理。阶段二初始化变量 预计算除数倍数immense result; result.LTGp,result.DCMp; immense z,prod[10]; z.LTGp,z.DCMp; z.symboltrue; z.LTGn0,z.DCMn0; for(int i1;i9;i) prod[i]IdProduct(y,char(i0));result存储最终的商初始为空z是当前余数初始为0prod[10]是预计算数组prod[i]就是除数乘以i的结果借助IdProduct提前算好1~9倍的除数后面二分试商时直接比较不用每次临时计算乘法这是一个非常实用的工程优化只需要9次单数字乘法就能为后续每一位试商节省多次乘法运算整体性能提升非常明显。阶段三整数部分逐位二分试商for(int ix.LTGn-1;i0;i--){ z.LTGpchar(x.LTGp[i])z.LTGp; z.LTGnz.LTGp.size(); z.rationalize(); int L0; if(zy){ int low1,high9,mid; while(lowhigh){ mid(lowhigh)/2; if(prod[mid]z){ Lmid; break; } else if(prod[mid]z) Lmid,lowmid1; else highmid-1; } zLess(z,prod[L]); } if(result.LTGp.empty()L0i0)continue; result.LTGpchar(L0)result.LTGp; } if(result.LTGp.empty()) result.LTGp0; result.LTGnresult.LTGp.size();这一段是整数长除法的主体完全对应通用原理的「从高位到低位逐位试商」。我们逐层拆解遍历方向for(int ix.LTGn-1;i0;i--)从被除数的最高位字符串最后一位开始往前遍历严格符合除法从高位到低位的计算顺序。落位操作z.LTGpchar(x.LTGp[i])z.LTGp;把被除数的当前位插到余数字符串的最头部。设计巧思倒序存储下把新字符插到余数头部等价于余数 余数 × 10 新位数字。因为头部是个位插入新个位后原来的每一位都自动左移一位权重×10。一行代码就完成了乘10加位的操作极其简洁。二分试商余数小于除数时商直接为0跳过二分余数≥除数时在1~9之间二分查找最大的满足条件的商因为预计算了prod数组每次判断只需要一次比较不需要临时算乘法减积更新余数zLess(z,prod[L]);找到正确商位后用余数减去「商×除数」得到新的余数进入下一轮迭代。商位追加与前导零跳过每算出一位商插到结果整数部分的头部因为先算的是高位后算的低位更靠近下标0商的高位如果是0且还没到最后一位就跳过不存避免前导零最后兜底如果整数部分全空补一个0阶段四小数部分补零迭代计算for(int i1;iDRsvD;i){ z.LTGp0z.LTGp; z.rationalize(); int L0; if(zy){ int low1,high9,mid; while(lowhigh){ mid(lowhigh)/2; if(prod[mid]z){ Lmid; break; } else if(prod[mid]z) Lmid,lowmid1; else highmid-1; } zLess(z,prod[L]); } result.DCMpchar(L0); } result.rationalize();小数部分的逻辑和整数部分高度对称只有两处区别补零代替落位z.LTGp0z.LTGp;没有更多被除数位可以落下所以每次补零等价于余数×10和落位的操作逻辑完全一致。商位追加到小数部分result.DCMpchar(L0);小数是正序存储先算出来的十分位是高位直接追加到末尾即可顺序天然匹配。循环次数由全局参数DRsvD控制也就是保留的小数位数。算完后调用归一化清理小数尾部的无效零。阶段五还原小数点位置while(RDCM--) result.Product_10(false); return result; }最后把之前记录的偏移量还回去循环调用Product_10(false)把结果的小数点左移 RDCM 位将整数除法的结果还原为原来实数除法的结果。至此整个无符号实数长除法流程全部完成。从实数转化、整数运算到小数点还原每一步都和通用原理严格对应。2.2 Centre_D符号中心化调度和乘法的Centre_P一样除法也有中心化调度函数负责除零前置检查和符号统一处理。immense Centre_D(immense x,immense y){ if(yimmense(0)) throw std::domain_error(Division by zero condition!); immense result; bool Rsymbol(x.symboly.symbol); x.abs(),y.abs(); resultDivision(x,y); result.symbolRsymbol; return result; }执行步骤非常清晰前置除零检查除数为零直接抛出异常计算结果符号同号为正异号为负两个数取绝对值调用无符号除法Division给结果回填符号返回这是标准的「符号与数值分离」范式和乘法的处理逻辑完全统一。2.3 运算符层统一接口封装上层的除法运算符全部复用Centre_D结构和加减乘完全一致inline immense immense::operator/(const immense other)const{ immense tother; return Centre_D(*this,t); } inline void immense::operator/(const immense other){ immense tother; *thisCentre_D(*this,t); }分层清晰调用统一所有四则运算保持了完全一致的代码风格。三、拓展除法的性能、优化与边界验证3.1 时间复杂度分析immense除法的时间复杂度为O( (n D) × m )其中n 是被除数整数位数D 是保留的小数位数由DRsvD决定m 是除数的位数每一位商需要约4次比较二分 log₂9 ≈ 3.17每次比较是 O(m)因此每一位商的开销是 O(m)总耗时线性于商的总位数。得益于预计算prod数组避免了每次试商都执行乘法常数因子很小实际运行效率比朴素试商法高很多。3.2 设计权衡点评二分试商 预计算倍数优点逻辑清晰边界条件少不易出错效率稳定缺点比高位估算法多1~2次比较但胜在可靠易维护对于教学级、工具级库来说二分试商是性价比极高的选择先整除数、再整被除数先把除数变成纯整数核心除法逻辑只面对整数除数更简洁用RDCM统一记录偏移量最后一次性还原小数点结构清晰倒序存储的落位巧思余数落位和补零都只需在字符串头部插字符一行代码完成「余数×10 位」的操作这是倒序存储在除法中的又一个显著优势3.3 自我验证练习可以用几个简单案例手动推演代码逻辑检验是否真正理解immense(123) / immense(5)→ 整数除法结果为 24.6immense(1) / immense(3)→ 无限循环小数保留DRsvD位 3immense(12.34) / immense(5.6)→ 实数除法验证小数点移位逻辑immense(0) / immense(5)→ 结果为 0符号强制为正四、本篇小结与下篇预告4.1 核心知识点回顾通用原理部分除法是逆向运算从高位到低位逐位试商核心框架是长除法三种试商方案朴素递减、高位估算、二分试商二分试商简洁稳定是工程常用方案实数除法通用流程除数整数化 → 被除数同步移位 → 整数长除 → 还原小数点除法必须设置精度截断因为除不尽的情况会产生无限小数符号规则同号得正异号得负必须做除零异常处理实现对照部分Division完整实现实数长除法除数整数化 → 预计算1~9倍除数 → 整数部分二分试商 → 小数部分补零试商 → 还原小数点 → 归一化倒序存储下余数落位和补零都可通过头部插字符实现极其简洁预计算prod数组大幅优化试商效率避免重复乘法运算Centre_D负责符号处理和除零检查遵循符号分离范式运算符层统一调用中心化函数接口风格与其他四则运算保持一致4.2 下篇预告到这里加减乘除四则运算就全部讲完了。但高精度库的能力远不止于此——取整、取模、幂运算、开方、超越函数这些高级功能都建立在四则运算的基础之上。下一篇我们进入取整与取模模块讲解floor、ceil、trunc、round四种取整函数的通用原理以及取模运算的定义与实现再对照immense的源码看它如何巧妙地利用Product_10移位操作实现任意指定位数的取整与取模。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑