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

Ana*_*and 26 java floating-point double equals

我正在阅读Joshua Bloch的有效java和第8项:当覆盖等于时遵守一般合同,这个陈述是写的

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

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

lin*_*ski 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        
Run Code Online (Sandbox Code Playgroud)

输出不正确,因为不是数字的东西根本不是数字,从数字比较的角度来看应该被视为相等.很明显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)
}
Run Code Online (Sandbox Code Playgroud)

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,则结果为false.

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

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

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

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

来自JLS 15.21.1.数值等式算子==和!=:

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

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

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

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

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

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

由于总排序(=,<,>,<=,>=)被许多重要的算法(参见实现Comparable接口的所有类),最好是用比较的方法,因为它会产生更一致的行为.

在IEEE-754标准的上下文中总排序的结果是正零和负零之间的差异.

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

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

Float 使用 IEEE-754 32位格式,Double 使用 IEEE-754 64位格式.


Boh*_*ian 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
Run Code Online (Sandbox Code Playgroud)

因此,在比较float值时,要使所有值(包括特殊Float.NaN值)保持一致,Float.compare()是最佳选择.

这同样适用于double.