我正在阅读 Joshua Bloch 的 Effective java,在第 8 项:Obey the general contract when overriding equals中,写了这个语句
对于浮点字段,使用 Float.compare 方法;对于双字段,请使用 Double.compare。由于 Float.NaN、-0.0f 和类似的 double 常量的存在,需要对 float 和 double 字段进行特殊处理;
有人可以举例说明为什么我们不能==
用于浮点或双重比较
我正在阅读 Joshua Bloch 的 Effective java,在第 8 项:Obey the general contract when overriding equals中,写了这个语句
对于浮点字段,使用 Float.compare 方法;对于双字段,请使用 Double.compare。由于 Float.NaN、-0.0f 和类似的 double 常量的存在,需要对 float 和 double 字段进行特殊处理;
有人可以举例说明为什么我们不能==
用于浮点或双重比较
来自 apidoc Float.compare
,:
比较两个指定的浮点值。返回的整数值的符号与调用将返回的整数的符号相同:
新浮点(f1).compareTo(新浮点(f2))
以数字方式比较两个 Float 对象。当应用于原始浮点值时,此方法执行的比较与 Java 语言数值比较运算符(<、<=、==、>= >)执行的比较有两种不同之处:
- 此方法认为 Float.NaN 等于其自身并且大于所有其他浮点值(包括 Float.POSITIVE_INFINITY)。
- 此方法认为 0.0f 大于 -0.0f。
这确保了此方法强加的 Float 对象的自然顺序与 equals 一致。
考虑以下代码:
System.out.println(-0.0f == 0.0f); //true
System.out.println(Float.compare(-0.0f, 0.0f) == 0 ? true : false); //false
System.out.println(Float.NaN == Float.NaN);//false
System.out.println(Float.compare(Float.NaN, Float.NaN) == 0 ? true : false); //true
System.out.println(-0.0d == 0.0d); //true
System.out.println(Double.compare(-0.0d, 0.0d) == 0 ? true : false);//false
System.out.println(Double.NaN == Double.NaN);//false
System.out.println(Double.compare(Double.NaN, Double.NaN) == 0 ? true : false);//true
输出不正确,因为不是数字的东西根本不是数字,从数字比较的角度来看,应该被视为相等。这也很清楚0=-0
。
让我们看看有什么Float.compare
作用:
public static int compare(float f1, float f2) {
if (f1 < f2)
return -1; // Neither val is NaN, thisVal is smaller
if (f1 > f2)
return 1; // Neither val is NaN, thisVal is larger
int thisBits = Float.floatToIntBits(f1);
int anotherBits = Float.floatToIntBits(f2);
return (thisBits == anotherBits ? 0 : // Values are equal
(thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
1)); // (0.0, -0.0) or (NaN, !NaN)
}
根据IEEE 754 浮点“单一格式”位布局返回指定浮点值的表示。 第 31 位(掩码 0x80000000 选择的位)表示浮点数的符号。位 30-23(由掩码 0x7f800000 选择的位)表示指数。位 22-0(由掩码 0x007fffff 选择的位)表示浮点数的有效位(有时称为尾数)。
如果参数为正无穷大,则结果为 0x7f800000。
如果参数为负无穷大,则结果为 0xff800000。
如果参数为 NaN,则结果为 0x7fc00000。
在所有情况下,结果都是一个整数,当将其提供给 intBitsToFloat(int) 方法时,将产生一个与 floatToIntBits 的参数相同的浮点值(除了所有 NaN 值都折叠为单个“规范”NaN 值)。
从JLS 15.20.1。数值比较运算符 <、<=、> 和 >=
由 IEEE 754 标准的规范确定的浮点比较的结果是:
如果任一操作数为 NaN,则结果为假。
除 NaN 之外的所有值都是有序的,负无穷小于所有有限值,正无穷大于所有有限值。
正零和负零被认为是相等的。例如,-0.0<0.0 为假,但 -0.0<=0.0 为真。
但是请注意,Math.min 和 Math.max 方法将负零视为严格小于正零。
对于操作数为正零和负零的严格比较,结果将是错误的。
由 IEEE 754 标准的规范确定的浮点比较的结果是:
浮点相等性测试根据 IEEE 754 标准的规则执行:
如果任一操作数为 NaN,则 == 的结果为假,但 != 的结果为真。实际上,当且仅当 x 的值为 NaN 时,测试 x!=x 为真。方法 Float.isNaN 和 Double.isNaN 也可用于测试值是否为 NaN。
正零和负零被认为是相等的。例如,-0.0==0.0 为真。
否则,相等运算符认为两个不同的浮点值不相等。特别是,有一个值表示正无穷大,一个值表示负无穷大;each 仅与自身比较相等,并且 each 与所有其他值比较不相等。
对于两个操作数都是NaN的相等比较,结果将是错误的。
由于许多重要的算法都使用了总排序(=
, <
, >
, <=
, >=
) (请参阅实现 Comparable 接口的所有类),因此最好使用 compare 方法,因为它会产生更一致的行为。
在 IEEE-754 标准的上下文中,总排序的结果是正零和负零之间的差异。
例如,如果您使用相等运算符而不是比较方法,并且有一些值集合并且您的代码逻辑根据元素的顺序做出一些决定,并且您以某种方式开始获得多余的 NaN 值,它们都会被视为不同的值,而不是相同的值。
这可能会在程序的行为中产生与 NaN 值的数量/比率成比例的错误。如果你有很多正零和负零,那只是一对会影响你的逻辑错误。
float
(and double
) 有一些特殊的位序列,它们是为非“数字”的特殊含义而保留的:
0xff800000
0x7f800000
0x7fc00000
当使用 与自身进行比较时,这些返回中的每一个0
(意味着它们是“相同的”)Float.compare()
,但以下使用的比较==
与 for 不同 Float.NaN
:
Float.NEGATIVE_INFINITY == Float.NEGATIVE_INFINITY // true
Float.POSITIVE_INFINITY == Float.POSITIVE_INFINITY // true
Float.NaN == Float.NaN // false
因此,在比较float
值时,使所有值(包括特殊Float.NaN
值)保持一致Float.compare()
是最佳选择。
这同样适用于double
。
比较浮点对象有两个原因:
==
运算符提供数学比较。它返回 falseNaN == NaN
和 true-0.f == +0.f
和例程提供对象比较compare
。compareTo
当将 NaN 与自身进行比较时,它们表明它是相同的(通过返回零)。当与 比较-0.f
时+0.f
,它们表明它们是不同的(通过返回非零)。