为什么我们不能使用'=='来比较两个浮点数或双数

时间:2013-07-27 13:20:34

标签: java floating-point double equals

我正在阅读Joshua Bloch的有效java和第8项:当重写等于时遵守一般合同,这句话是写的

  

对于float字段,使用Float.compare方法;对于双字段,请使用   Double.compare。浮法和双场的特殊处理   由Float.NaN,-0.0f和类似的双重存在所必需的   常数;

有人可以用例子向我解释为什么我们不能将==用于浮动或双重比较

3 个答案:

答案 0 :(得分:18)

来自apidoc,Float.compare

  

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

     

new Float(f1).compareTo(new Float(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值都折叠为a单一的“规范”NaN值)。

来自JLS 15.20.1. Numerical Comparison Operators <, <=, >, and >=

  

根据IEEE 754标准规范确定的浮点比较结果如下:

     
      
  • 如果任一操作数为NaN,则结果为false。

  •   
  • NaN以外的所有值都是有序的,负无穷大小于所有有限值,正无穷大大于所有有限值。

  •   
  • 正零和负零被认为是相等的。例如,-0.0 <0.0为假,但-0.0 <= 0.0为真。

  •   
  • 但请注意,方法Math.min和Math.max将负零视为严格小于正零。

  •   

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

来自JLS 15.21.1. Numerical Equality Operators == and !=

  

根据IEEE 754标准规范确定的浮点比较结果如下:

     

根据IEEE 754标准的规则执行浮点相等测试:

     
      
  • 如果任一操作数为NaN,则==的结果为false,但!=的结果为true。实际上,当且仅当x的值是NaN时,测试x!= x才为真。方法Float.isNaN和Double.isNaN也可用于测试值是否为NaN。

  •   
  • 正零和负零被认为是相等的。例如,-0.0 == 0.0为真。

  •   
  • 否则,等于运算符会将两个不同的浮点值视为不相等。特别是,有一个值代表正无穷大,一个值代表负无穷大;每个比较仅与自身相等,每个比较不等于所有其他值。

  •   

对于两个操作数均为NaN的相等比较,结果将是错误的。

由于许多重要算法使用total ordering (=, <, >,<=, >=)(请参阅all the classes that implement the Comparable interface),因此最好使用compare方法,因为它会产生更一致的行为。

total ordering in the context of the IEEE-754 standard的结果是正负零之间的差异。

例如,如果使用等于运算符而不是compare方法,并且有一些值集合,并且您的代码逻辑根据元素的顺序做出一些决定,并且您以某种方式开始获得剩余的NaN值将被视为不同的值而不是相同的值。

这可能会在程序行为中产生与NaN值的数量/速率成比例的错误。如果你有很多正负零,那只是一对会影响你的逻辑错误。

浮点uses IEEE-754 32位格式和双uses IEEE-754 64位格式。

答案 1 :(得分:5)

float(和double)有一些特殊的比特序列保留用于非“数字”的特殊含义:

  • 负无穷大,内部代表0xff800000
  • 正无穷大,内部代表0x7f800000
  • 不是数字,内部代表0x7fc00000

当使用0与自身进行比较时,每个回复Float.compare()(意味着它们“相同”),但使用==的以下比较与此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

答案 2 :(得分:2)

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

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

==运算符提供数学比较。它为NaN == NaN返回false,对-0.f == +0.f

返回true

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