Java浮點數相等性的判斷
問題描述如下:
給定兩個變量double a、double b,它們對應的裝箱類型變量分別是Double x、Double y,問:
- 是否存在一組a、b、x、y,滿足 a==b && !x.equals(y) ?
- 是否存在一組a、b、x、y,滿足 a!=b && x.equals(y) ?
乍看之下似乎是不可能的,實際上確實存在這樣的值,參考以下代碼
public static void main() {
double a = 0.0;
double b = -0.0;
double c = Double.NaN;
double d = Double.NaN;
Double x = a;
Double y = b;
Double z = c;
Double w = d;
System.out.println(a == b); //輸出true
System.out.println(x.equals(y)); //輸出false
System.out.println(c == d); //輸出false
System.out.println(w.equals(z)); //輸出true
}
Double類型equals
方法的實現和 ==
操作符邏輯有所不同。
先看==
操作符,以Java8為例,根據Java語言規范15.21.1,對於浮點數相等性判斷,遵從IEEE 754規范:
- 只要有一個操作數是
NaN
,==
表達式的結果總是false,!=
表達式的結果總是true。實際上,當且僅當x
的值為NaN
時,表達式x!=x
為真。可以使用Float.NaN
方法或者Double.NaN
方法判斷一個值是否是NaN
。 - 正數0與負數0相等,例如表達式
0.0==-0.0
為真。 - 除此之外,兩個不同的浮點數使用
==
或!=
操作符判斷相等性時,會認為它們不相等。尤其是,一個值表示正無窮,一個值表示負無窮;如果它們與自身比較,是相等的;與其他值比較,是不相等的。
再看JDK中Double的equals方法實現:
public boolean equals(Object obj) {
return (obj instanceof Double)
&& (doubleToLongBits(((Double)obj).value) ==
doubleToLongBits(value));
}
/**
* Returns a representation of the specified floating-point value
* according to the IEEE 754 floating-point "double
* format" bit layout.
*
* <p>Bit 63 (the bit that is selected by the mask
* {@code 0x8000000000000000L}) represents the sign of the
* floating-point number. Bits
* 62-52 (the bits that are selected by the mask
* {@code 0x7ff0000000000000L}) represent the exponent. Bits 51-0
* (the bits that are selected by the mask
* {@code 0x000fffffffffffffL}) represent the significand
* (sometimes called the mantissa) of the floating-point number.
*
* <p>If the argument is positive infinity, the result is
* {@code 0x7ff0000000000000L}.
*
* <p>If the argument is negative infinity, the result is
* {@code 0xfff0000000000000L}.
*
* <p>If the argument is NaN, the result is
* {@code 0x7ff8000000000000L}.
*
* <p>In all cases, the result is a {@code long} integer that, when
* given to the {@link #longBitsToDouble(long)} method, will produce a
* floating-point value the same as the argument to
* {@code doubleToLongBits} (except all NaN values are
* collapsed to a single "canonical" NaN value).
*
* @param value a {@code double} precision floating-point number.
* @return the bits that represent the floating-point number.
*/
public static long doubleToLongBits(double value) {
long result = doubleToRawLongBits(value);
// Check for NaN based on values of bit fields, maximum
// exponent and nonzero significand.
if ( ((result & DoubleConsts.EXP_BIT_MASK) ==
DoubleConsts.EXP_BIT_MASK) &&
(result & DoubleConsts.SIGNIF_BIT_MASK) != 0L)
result = 0x7ff8000000000000L;
return result;
}
Double的equals方法,通過==
操作符,判斷兩個對象的doubleToLongBits返回值是否相等,來覺得兩個對象是否相等。方法注釋中有一句
If the argument is NaN, the result is 0x7ff8000000000000L
所以使用equals判斷兩個NaN
時,結果為真。
對於其他情況,根據value的二進制表示,doubleToLongBits
返回對應的long值。而0.0
和-0.0
的符號位不同,所以二進制表示也不同,doubleToLongBits
的結果也不同。