为什么我们不能使用“==”来比较两个浮点数或双精度数
我正在阅读Joshua Bloch的有效java,并在第8项:当覆盖相等时遵守总合同,这个陈述是写的
对于浮点数字段,请使用 Float.compare 方法;对于双精度字段,请使用 Double.compare。浮点和双场的特殊处理由于浮点数.NaN,-0.0f和类似的双常数的存在而成为必要;
有人可以用例子来解释我为什么我们不能用于浮点或双重比较吗?==
我正在阅读Joshua Bloch的有效java,并在第8项:当覆盖相等时遵守总合同,这个陈述是写的
对于浮点数字段,请使用 Float.compare 方法;对于双精度字段,请使用 Double.compare。浮点和双场的特殊处理由于浮点数.NaN,-0.0f和类似的双常数的存在而成为必要;
有人可以用例子来解释我为什么我们不能用于浮点或双重比较吗?==
来自 apidoc, Float.compare
:
比较两个指定的浮点值。返回的整数值的符号与调用将返回的整数的符号相同:
new Float(f1).compareTo(new Float(f2))
以数字方式比较两个 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)
}
根据 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,则 == 的结果是假的,但 != 的结果是 true。事实上,检验 x!=x 为真,当且仅当 x 的值为 NaN。Float.isNaN 和 Double.isNaN 方法也可用于测试值是否为 NaN。
正零和负零被视为相等。例如,-0.0==0.0 为真。
否则,相等运算符会将两个不同的浮点值视为不相等。特别是,有一个值表示正无穷大,一个值表示负无穷大;每个值仅与自身相等,并且每个值都与所有其他值进行比较。
对于两个操作数均为 NaN 的相等比较,结果将是错误的。
由于许多重要的算法都使用总排序(=
,<
,
>
,<=,>=
)(请参阅实现Compaable接口的所有类),因此最好使用compare方法,因为它将产生更一致的行为。
在IEEE-754标准的上下文中,总排序的结果是正零和负零之间的差异。
例如,如果您使用相等运算符而不是 compare 方法,并且有一些值集合,并且您的代码逻辑根据元素的顺序做出一些决策,并且您以某种方式开始获得多余的 NaN 值,它们都将被视为不同的值,而不是相同的值。
这可能会在程序的行为中产生与NaN值的数量/速率成比例的错误。如果你有很多正零和负零,那只是一对错误影响你的逻辑。
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