float 的二进制表示本质导致精度丢失,因其遵循 IEEE 754 标准,0.1 和 0.2 在二进制中为无限循环小数,23 位尾数截断后产生固有误差,故 0.1f + 0.2f ≠ 0.3f;double 同理,仅误差更小;应使用 epsilon 比较或 BigDecimal 等方案规避。

float 的二进制表示本质导致精度丢失
Java 中的 float 遵循 IEEE 754 单精度浮点数标准,用 32 位表示:1 位符号位 + 8 位指数位 + 23 位尾数(有效数字)位。关键在于:它只能精确表示那些能写成 m × 2e 形式的十进制小数(其中 m、e 为整数),而像 0.1 和 0.2 这类十进制有限小数,在二进制中是无限循环小数。
例如:
- 0.110 = 0.000110011001100110011001100…2(循环节 "0011")
- 0.210 = 0.00110011001100110011001100…2(同样无限循环)
由于尾数只有 23 位,系统必须截断或舍入,造成固有误差 —— 这不是 Java 的 bug,而是所有遵循 IEEE 754 的语言(C、Python、JavaScript 等)共有的底层行为。
0.1+0.2 在 float 中的实际计算过程
以 float 类型为例(Java 中 float a = 0.1f + 0.2f;):
立即学习“Java免费学习笔记(深入)”;
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 0.1f 被存储为最接近的可表示 float 值:≈ 0.10000000149011612
- 0.2f 被存储为 ≈ 0.20000000298023224
- 两者相加得 ≈ 0.3000000044703483
- 而字面量
0.3f实际存储为 ≈ 0.30000001192092896
因此 0.1f + 0.2f == 0.3f 返回 false —— 它们在内存中是两个不同的 bit 模式,差值虽小(约 7×10−8),但比较时逐位不等。
为什么 double 也没完全解决?
double 使用 64 位(52 位尾数),精度更高,但问题本质未变:0.1 和 0.2 仍是二进制无限循环小数。只是截断位置更靠后,误差更小。
-
0.1 + 0.2 == 0.3(double 默认字面量)仍为 false - 实际值:0.1+0.2 → 0.30000000000000004,而 0.3 → 0.29999999999999999
这说明:只要用二进制浮点数表示十进制小数,就无法彻底避免该问题;只是 double 让“看起来相等”的错觉维持得更久。
正确处理方式:不直接比较浮点数相等
业务中应避免 == 判断浮点结果,改用误差容忍(epsilon)比较:
-
Math.abs(a - b) < 1e-6(适用于一般精度要求) - 对货币等场景,改用
BigDecimal,它用字符串构造、十进制运算,无二进制表示误差:new BigDecimal("0.1").add(new BigDecimal("0.2")).equals(new BigDecimal("0.3")) // true - 若需高性能且可控,可转为整数运算(如金额存分为单位)
核心原则:把浮点数当作“近似值”,而非精确数;比较、累加、存储敏感数据时,主动规避其二进制局限性。

















