0.1+0.2≠0.3是ieee 754标准下二进制浮点数固有局限所致,因0.1和0.2在二进制中为无限循环小数,float(23位尾数)和double(52位尾数)截断存储必然引入舍入误差,运算后误差累积导致结果不等。

Java 中的浮点数精度问题不是 bug,而是 IEEE 754 标准在有限位二进制下表示十进制小数的必然结果。0.1 + 0.2 ≠ 0.3 的现象,根源在于 0.1 和 0.2 在二进制中都是无限循环小数,存储时被截断,误差在运算中累积。要真正用好浮点数,得理解它怎么存、为什么不准、什么场景该换方案。
IEEE 754 是怎么存浮点数的
Java 的 float 和 double 都严格遵循 IEEE 754 标准,本质是用科学计数法的二进制形式表达:V = (−1)ˢ × M × 2ᴱ。
- float(32位):1 位符号位 + 8 位指数位 + 23 位尾数位,约 6–7 位十进制有效数字
- double(64位):1 位符号位 + 11 位指数位 + 52 位尾数位,约 15–16 位十进制有效数字
- 十进制小数如 0.1,转二进制是 0.0001100110011…(0011 循环),无法用有限位精确表示
- 存储时强制截断尾数,造成初始误差——这个误差从赋值那一刻就已存在
常见精度陷阱与典型表现
这些现象背后都是同一套机制在起作用,只是暴露方式不同:
- 直接比较失败:
0.1f == 0.1d返回 false,因为 float 和 double 对 0.1 的近似值不同 - 累加偏差:循环执行
sum += 0.110 次,结果可能不是 1.0,而是 0.9999999999999999 或 1.0000000000000002 - 大数丢失精度:float 存 16777217 会变成 16777216,因为超出其可精确表示的整数范围(2²⁴ = 16777216)
- 除零不报错:
1.0 / 0.0得Infinity,0.0 / 0.0得NaN,需主动检查
正确比较浮点数的方法
永远不要用 == 直接比较两个浮点数。应使用误差容忍(epsilon)或封装工具:
- 手动容差判断:
Math.abs(a - b) (根据业务精度选合适阈值) - 用
Double.compare(a, b) == 0,它按 IEEE 754 规则处理 NaN 和符号零 - JUnit 中推荐
assertEquals(expected, actual, delta) - 避免
new Double(x).equals(new Double(y)),包装类 equals 会比较对象引用而非数值
需要绝对精度时的替代方案
金融、计费、审计等场景,必须放弃 float/double,改用更可靠的表示方式:
-
BigDecimal:首选方案。用字符串构造(
new BigDecimal("19.99")),避免 double 初始化污染;所有运算调用.add()、.multiply()等方法;除法必须指定 scale 和RoundingMode - 整型缩放:金额统一用“分”存储为 long,如 19.99 元 → 1999 分,全程整数运算,零误差
-
第三方库:Apache Commons Math 的
Precision.equals(a, b, 1e-12)提供健壮比较;Google Guava 的DoubleMath.fuzzyEquals()
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











