0.1+0.2≠0.3是ieee 754标准下二进制无法精确表示十进制小数导致的固有误差,因0.1=1/10含质因数5,二进制为无限循环小数0.000110011…,双精度仅存52位尾数致舍入误差,相加后得0.30000000000000004,应改用容差比较math.abs(a-b)

这不是代码写错了,而是所有遵循 IEEE 754 标准的语言(JavaScript、Java、Python、C++ 等)共有的底层限制。根本原因在于:0.1 和 0.2 在二进制中无法被精确表示,存储时被迫截断,运算后误差显现。
识别根源:从二进制转换开始看起
十进制小数能否被二进制精确表达,取决于其分母是否只含质因数 2。0.1 = 1/10,而 10 = 2 × 5,含质因数 5 → 无法精确表达。用“乘 2 取整”法转换:
- 0.1 × 2 = 0.2 → 整数位 0
- 0.2 × 2 = 0.4 → 整数位 0
- 0.4 × 2 = 0.8 → 整数位 0
- 0.8 × 2 = 1.6 → 整数位 1,余 0.6
- 0.6 × 2 = 1.2 → 整数位 1,余 0.2
- ……循环出现 0011
结果是无限循环二进制小数:0.0001100110011…。IEEE 754 双精度浮点数仅保留 52 位尾数,超出部分被舍入或截断 → 存储的已是近似值。两个近似值相加,误差叠加,最终输出 0.30000000000000004。
避免直接等号判断:用容差比较替代 ===
在条件判断、断言、状态比对等场景中,绝不可用 === 或 == 直接比较浮点结果。应引入误差容忍机制:
- 使用
Math.abs(a - b) (推荐,ES6+ 原生支持) - 对金融类场景,建议自定义更宽松的容差,如
1e-10或1e-12,避免受计算路径影响 - 注意:
Number.EPSILON ≈ 2.22e-16,适用于多数科学计算,但不适用于高精度业务逻辑
关键业务必须精确:选对数据类型与工具库
涉及金额、库存、计费、税率等不可累积误差的场景,必须放弃 number 类型:
-
JavaScript:用
decimal.js或big.js,以字符串初始化避免初始误差
✅ 正确:new Decimal('0.1').plus('0.2').equals('0.3')
❌ 错误:new Decimal(0.1).plus(0.2)(传入 number 已失真) -
Java:强制使用
BigDecimal,且构造函数务必传字符串
✅ 正确:new BigDecimal("0.1").add(new BigDecimal("0.2"))
❌ 错误:new BigDecimal(0.1)(double 构造器自带误差) -
Python:优先用
decimal.Decimal,设置getcontext().prec控制有效位数;避免float初始化
前端展示与输入环节的防错设计
用户看到的“0.3”和输入的“0.1”都是十进制语义,但 JS 内部立刻转为二进制浮点——这个转换点就是误差入口:
- 表单输入后,立即用正则或
parseFloat+toFixed(n)截取,再转字符串供计算库使用 - 展示前统一格式化:用
Number(val).toFixed(2)或Intl.NumberFormat,而非依赖原始 number 值渲染 - 禁止将浮点数用作对象 key 或 Map 键(如
{[0.1 + 0.2]: 'x'}),极易因精度差异导致键丢失










