ieee 754 通过符号位、指数位和尾数位三部分存储小数:float 为32位(1+8+23),double 为64位(1+11+52);十进制0.1和0.2在二进制中均为无限循环小数,受限于尾数位长度必须截断,导致固有精度误差,故0.1f + 0.2f ≠ 0.3f,且所有遵循该标准的语言均存在此现象。

IEEE754 是怎么存小数的
Java 的 float 和 double 都严格遵循 IEEE754 标准,把一个数拆成三块:符号位(正负)、指数位(决定小数点位置)、尾数位(决定精度)。比如 double 是 64 位:1 位符号 + 11 位指数 + 52 位尾数。它不直接存“0.1”,而是用二进制科学计数法逼近——而十进制的 0.1 恰好是二进制无限循环小数 0.0001100110011…₂,必须截断到 52 位,一截就丢精度。
为什么 0.1 + 0.2 ≠ 0.3
这不是 Java 的 bug,是所有遵循 IEEE754 的语言共有的底层限制。0.1 和 0.2 在内存里各自已存在微小误差,相加后误差叠加,结果变成 0.30000000000000004。同理,1.0 - 0.9 得到的是 0.09999999999999998。这类问题在金融、计费、物理模拟等对数值稳定性敏感的场景中尤其危险。
浮点数比较不能用 ==
直接用 == 判断两个浮点数是否相等,大概率出错。正确做法是判断它们的差值是否小于一个极小阈值(epsilon):
- 定义容差,如
final double EPS = 1e-10; - 用
Math.abs(a - b) 替代 <code>a == b - 注意:对极大或极小数值,EPS 应随数量级动态调整,否则可能失效
需要精确计算时用 BigDecimal
当业务要求“一分都不能错”(如金额、税率、库存扣减),必须绕过 float/double:
- 构造时用字符串,
new BigDecimal("10.1"),避免new BigDecimal(10.1)把 double 的误差直接带进来 - 四则运算用
.add()、.subtract()、.multiply()、.divide(..., scale, RoundingMode) - 除法必须指定小数位数和舍入模式,例如
RoundingMode.HALF_UP(四舍五入) - 比较用
.compareTo(),不是.equals()(后者会比较 scale,0.10 和 0.1 不等)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











