为什么 1 亿 + 1 还是 1 亿?揭秘计算机里的“大数吃小数”现象

张开发
2026/4/5 1:36:59 15 分钟阅读

分享文章

为什么 1 亿 + 1 还是 1 亿?揭秘计算机里的“大数吃小数”现象
为什么 1 亿 1 还是 1 亿揭秘计算机里的“大数吃小数”现象大家好我是正在死磕《计算机组成原理》的计算机专业学生。最近在学习IEEE 754 浮点数标准时我发现了一个非常反直觉的现象在计算机里大数加小数结果可能还是大数。这听起来很荒谬就像如果你往太平洋里倒一杯水太平洋的水位竟然纹丝不动但在二进制的世界里这却是每天都在发生的残酷现实。今天我们就来扒一扒这个让无数程序员掉过头发的坑——“大数吃小数”。 一个反直觉的实验首先让我们看一段简单的 C 语言代码。假设我们有一个很大的数134217728这是2272^{27}227我们给它加上1。#includestdio.hintmain(){floatbig134217728.0f;floatsmall1.0f;floatresultbigsmall;printf(big %.0f\n,big);printf(result %.0f\n,result);// 判断是否相等if(resultbig){printf( 恐怖大数吃掉了小数结果竟然相等\n);}return0;}运行结果big 134217728 result 134217728 恐怖大数吃掉了小数结果竟然相等发生了什么数学上1342177281134217729134217728 1 1342177291342177281134217729但在单精度浮点数float的世界里1被无情地“吞噬”了。 为什么会发生“吞噬”要理解这个问题我们需要戴上“二进制眼镜”看看计算机底层到底是怎么存储这些数字的。计算机通常遵循IEEE 754 标准。对于单精度浮点数32位它的结构是这样的符号位 (1位)指数位 (8位)尾数位 (23位)决定正负决定数量级决定精度关键点就在这里尾数位只有 23 位。这意味着浮点数能存储的有效数字是有限的。这就好比科学计数法如果你只能保留 7 位有效数字那么1.3421772×1081.3421772 \times 10^81.3421772×108后面再想加个111由于111相对于10810^8108太小了根本挤不进这 7 位有效数字里只能被舍去。⚙️ 深度解析对阶操作的残酷真相当计算机执行big small时它不能直接加。就像你要把“米”和“厘米”相加必须先统一单位。在浮点数加法中这个过程叫对阶。规则小阶向大阶看齐。让我们看看刚才的例子在二进制里发生了什么大数 (134217728)二进制科学计数法1.0×2271.0 \times 2^{27}1.0×227指数是 27。小数 (1)二进制科学计数法1.0×201.0 \times 2^01.0×20指数是 0。对阶过程为了相加计算机必须把小数的指数也变成 27。怎么做把小数的尾数向右移动 27 位1.0×20→0.00...01×2271.0 \times 2^0 \rightarrow 0.00...01 \times 2^{27}1.0×20→0.00...01×227(小数点向右移了27位)悲剧发生了我们的尾数寄存器只有23位加上隐含的1位实际精度约24位。要把1对齐到2272^{27}227的量级需要右移 27 位。这意味着1的有效位被移到了第 28 位远远超出了寄存器的容量。结果计算机只能把移出去的部分直接截断丢弃。于是小数变成了 0。1342177280134217728134217728 0 1342177281342177280134217728这就是大数吃小数的本质因为精度有限小数的有效位在“对阶”过程中被移出了存储范围导致精度丢失。 这种误差会带来什么后果别以为这只是理论在实际开发中这可能会引发严重 Bug。场景累加求和假设你在写一个统计系统要累加 1000 万个0.1。# 伪代码sum0.0foriinrange(10000000):sum0.1print(sum)# 预期1000000.0# 实际可能999999.9xxxx 或者偏差很大随着sum越来越大后面加入的0.1相对于巨大的sum来说越来越微不足道。当sum大到一定程度0.1就再也加不进去了这就导致了计算结果严重偏小。️ 我们该如何防御作为计算机专业的学生我们不仅要懂原理还要会解决问题。这里有几个锦囊1. 升级装备使用 doubledouble有 52 位尾数精度比float高得多约 15-17 位十进制有效数字。虽然不能彻底根除但能大大推迟“大数吃小数”发生的阈值。2. 改变策略排序后累加如果你必须累加大量浮点数先排序从小到大加先把所有的小数加在一起让它们“抱团取暖”积攒成一个大一点的数再去和更大的数相加。这样可以最大程度保留精度。3. 终极武器Kahan 求和算法这是一个非常巧妙的算法它能通过一个补偿变量把每次加法丢失的“误差”记录下来并在下一次加法中补回来。// Kahan 求和算法核心逻辑floatkahan_sum(float*values,intn){floatsum0.0f;floatc0.0f;// 补偿变量记录丢失的低位for(inti0;in;i){floatyvalues[i]-c;floattsumy;c(t-sum)-y;// 捕获刚才加法中丢失的误差sumt;}returnsum;}4. 金融领域的特例BigDecimal如果你是在做电商、金融系统涉及到钱金额千万不要用 float/double。请直接使用BigDecimal(Java) 或Decimal(Python)或者干脆用整数来存储“分”。 总结计算机不是万能的它也有“视力模糊”的时候。浮点数有精度限制float只有约 7 位有效数字。对阶导致截断大数加小数时小数会被右移超出位宽的部分被丢弃。避免方法排序累加、Kahan 算法、使用高精度类型。理解了这些下次面试官问你“0.1 0.2 等于多少”或者“为什么浮点数会有误差”时你就可以从容地画出二进制位图给他上一课了觉得有用的话点个赞和在看支持我继续分享硬核计算机知识

更多文章