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

我正在阅读约书亚·布洛赫(Joshua Bloch)撰写的Effective java,而在第8条中:当重写equals时服从一般合约

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

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

从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 

输出是不正确的,因为不是一个数字的东西,不是一个数字,从数字比较的angular度来看,应该被视为相等。 同样清楚的是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浮点“单一格式”位布局返回指定浮点值的表示forms。 位31(由掩码0x80000000select的位)表示浮点数的符号。 位30-23(由掩码0x7f800000select的位)表示指数。 位22-0(由掩码0x007fffffselect的位)表示浮点数的有效数(有时称为尾数)。

如果参数是正无穷大,结果是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标准的规定,浮点比较的结果是:

浮点平等testing按照IEEE 754标准的规则进行:

  • 如果两个操作数都是NaN,那么==的结果是false,但是!=的结果是true。 事实上,当且仅当x的值是NaN时,testingx!= x才是真的。 方法Float.isNaN和Double.isNaN也可以用来testing一个值是否为NaN。

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

  • 否则,两个不同的浮点值被相等运算符视为不相等的。 特别是,有一个值代表正无穷大,一个代表负无穷大; 每个比较自己都相等,而且每个比较值都不相等。

对于两个操作数都是NaN的等式比较,结果是错误的。

由于许多重要algorithm(请参阅实现Comparable接口的所有类 )都使用了总sorting( =<><=>= ) ,所以最好使用compare方法,因为它会产生更一致的行为。

在IEEE-754标准中, 总sorting的结果是正负零之间的差异。

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

这可能会导致程序的行为与NaN的数量/比率成正比。 如果你有很多积极和消极的零,那只是一对影响你的逻辑与错误。

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

float (和double )有一些特殊的位序列,它们被保留为不是“数字”的特殊含义:

  • 负无限,内部表示0xff800000
  • 正无限,内部表示0x7f800000
  • 不是数字,内部表示0x7fc00000

与使用Float.compare()进行比较,每个返回0都是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()是最好的select。

这同样适用于double

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

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

==运算符提供math比较。 它对于NaN == NaN返回false,对于-0.f == +0.f返回true

comparecompareTo例程提供对象比较。 当比较一个NaN到它自己时,它们表明它是相同的(通过归零)。 当比较-0.f+0.f ,它们表明它们是不同的(通过返回非零)。