浮点数的坑大多数人都听过"不要用==比较",但很少有人真的验证过 NaN 和正负零这两个特殊值在不同比较方式下,答案真实会不会自相矛盾。这篇真实跑一遍,结果确实相当反直觉。
IEEE 754 规定 NaN 不等于任何值,包括它自己——这条规则在 Java 里是否被完整遵守到底。
DoublePrecisionDemo.java double nan = 0.0 / 0.0; System.out.println(nan == nan); Double nanBoxed = Double.valueOf(nan); System.out.println(nanBoxed.equals(nanBoxed));
0.0/0.0 = NaN nan == nan: false Double.isNaN(nan): true nanBoxed.equals(nanBoxed): true(装箱后equals()真实认为NaN等于自己)
正零和负零在数学上是同一个值,但在 IEEE 754 的位表示里是两个不同的模式——真实验证 Java 各种比较方式如何处理这个差异。
0.0 == -0.0: true Double.valueOf(0.0).equals(Double.valueOf(-0.0)): false(equals()真实区分正负零) Double.compare(0.0, -0.0) = 1(compare真实认为正零大于负零) 1.0/0.0 = Infinity, 1.0/-0.0 = -Infinity(除以正负零结果不同)
真实结果再次证明基本类型比较和 equals()/compareTo() 走的是两套不同规则:== 真实认为 0.0 和 -0.0 相等(这是 IEEE 754 对数值比较的规定);但 Double.equals() 和 Double.compare() 真实基于位模式做比较,0.0 和 -0.0 的符号位不同,被真实认为是不同的、可排序的两个值(正零更大)。这个差异在真实数值计算里可能有实际影响:1.0/0.0 和 1.0/-0.0 真实得到方向相反的无穷大,如果代码里混用了 == 判断相等性和依赖除法结果的符号,可能会遇到不一致的逻辑。
最后用一个日常场景收尾:循环累加0.1十次,验证结果是否真的等于1.0。
10次累加0.1 = 0.9999999999999999 sum == 1.0: false Math.abs(sum - 1.0) < 1e-9: true(正确的浮点数比较方式) float 1/3 = 0.33333334 double 1/3 = 0.3333333333333333 (double) f == d: false(float精度更低,转成double后依然不相等)
真实结果再次印证了浮点数二进制表示带来的天然误差:十次累加 0.1 真实得到 0.9999999999999999,不是精确的 1.0,直接用 == 比较真实为 false;而用误差容忍区间(epsilon)比较才是真实可靠的浮点数相等性判断方式,这也是系列前面 BigDecimal 精度那篇建议在需要精确计算(比如金额)的场景改用 BigDecimal 的根本原因。
每一行都是真实运行这段代码得到的结果。