30

我正在阅读 Joshua Bloch 的 Effective java,在第 8 项:Obey the general contract when overriding equals中,写了这个语句

对于浮点字段,使用 Float.compare 方法;对于双字段,请使用 Double.compare。由于 Float.NaN、-0.0f 和类似的 double 常量的存在,需要对 float 和 double 字段进行特殊处理;

有人可以举例说明为什么我们不能==用于浮点或双重比较

4

3 回答 3

20

来自 apidoc Float.compare,:

比较两个指定的浮点值。返回的整数值的符号与调用将​​返回的整数的符号相同:

新浮点(f1).compareTo(新浮点(f2))

Float.compareTo

以数字方式比较两个 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)
}

Float.floatToIntBits

根据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 方法将负零视为严格小于正零。

对于操作数为正零和负零的严格比较,结果将是错误的。

JLS 15.21.1。数值等式运算符 == 和 !=

由 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使用IEEE-754 32 位格式,而 Double使用IEEE-754 64 位格式。

于 2013-07-27T13:32:04.200 回答
7

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

于 2013-07-27T13:28:04.643 回答
2

比较浮点对象有两个原因:

  • 我正在做数学,所以我想比较它们的数值。在数值上,–0 等于 +0,而 NaN 不等于任何东西,甚至不等于它本身,因为“等于”是只有数字才有的属性,而 NaN 不是数字。
  • 我正在使用计算机中的对象,因此我需要区分不同的对象并将它们按顺序排列。例如,这对于对树或其他容器中的对象进行排序是必需的。

==运算符提供数学比较。它返回 falseNaN == NaN和 true-0.f == +0.f

和例程提供对象比较comparecompareTo当将 NaN 与自身进行比较时,它们表明它是相同的(通过返回零)。当与 比较-0.f+0.f,它们表明它们是不同的(通过返回非零)。

于 2013-07-27T14:07:03.817 回答