为什么我们不能用'=='来比较两个浮点数或双数
我正在阅读约书亚·布洛赫(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
compare
和compareTo
例程提供对象比较。 当比较一个NaN到它自己时,它们表明它是相同的(通过归零)。 当比较-0.f
到+0.f
,它们表明它们是不同的(通过返回非零)。