为什么Double.NaN == Double.NaN的结果是false?

162

我刚刚在学习OCPJP问题时发现了这段奇怪的代码:

public static void main(String a[]) {
    System.out.println(Double.NaN==Double.NaN);
    System.out.println(Double.NaN!=Double.NaN);
}

当我运行这段代码时,我得到了:

false
true

当我们比较两个看起来相同的东西时,为什么输出是falseNaN是什么意思?


8
这真的很奇怪。因为Double.NaN是静态不可变的,使用==进行比较应该返回true。对于这个问题要点一个赞。 - Stephan
2
在Python中也是如此:In [1]: NaN==NaN Out[1]: False - tdc
58
所有遵循IEEE 754标准的语言都是如此。 - zzzzBov
4
直觉上,“Hello”不是一个数字,true(布尔值)也不是一个数字。 NaN != NaN 的原因和“Hello”!= true相同。 - Kevin
3
如果Double.NaN是类型为java.lang.Double的话,与Double.NaN==Double.NaN的比较确实应该返回true。然而,它的类型是原始类型double,因此适用于double的运算符规则(根据IEEE 754标准要求不相等),这些规则在回答中有所解释。 - sleske
显示剩余4条评论
10个回答

141

NaN代表“不是一个数字”。

Java语言规范(JLS)第三版指出:

溢出的运算结果为有符号无穷大,下溢的运算结果为非规格化值或带符号零,没有数学确定结果的运算返回NaN。所有以NaN作为操作数的数值运算的结果都是NaN。正如已经描述过的那样,NaN是无序的,因此涉及一个或两个NaN的数值比较运算返回false,任何涉及NaN的!=比较运算都返回true,包括当x为NaN时的x!=x


4
大部分是正确的。与符合IEEE标准的浮点数进行比较将产生“false”。因此,该标准与Java不同之处在于IEEE要求“(NAN!= NAN) == false”。 - Drew Dormann
3
打开这个潘多拉魔盒——你认为“IEEE要求(NAN!= NAN)== false”是在哪里? - Supervisor

62

NaN的定义是不等于包括它本身在内的任何数字。这是IEEE 754标准的一部分并由CPU/FPU实现,JVM不需要添加任何逻辑来支持它。

http://en.wikipedia.org/wiki/NaN

与NaN进行比较时,即使与自身比较也始终返回无序结果。... 等式和不等式谓词是非信号的,因此x = x返回false可用于测试x是否为安静NaN。

Java将所有NaN视为安静NaN。


1
它是由CPU实现的,还是像Bohemian提到的那样硬连在JVM中? - Naweed Chougle
3
JVM必须调用能正确实现它的东西。在PC上,CPU本身完成所有工作。在没有此支持的机器上,JVM必须实现它。(我不知道有这样的机器) - Peter Lawrey
在过去,当8087是一个选项时,C库中包含了一个浮点数模拟器。像JVM这样的程序无论如何都不需要担心它。 - user207421

51

为什么会这样

NaN表示“不是一个数字”。什么不是数字?任何东西都可能不是。你可以在一边放任何东西,在另一边放任何东西,因此没有保证两者相等。 NaN通过 Double.longBitsToDouble(0x7ff8000000000000L) 计算得出,正如你可以在 longBitsToDouble 的文档中看到的:

如果参数是范围为 0x7ff0000000000001L0x7fffffffffffffffL 或范围为 0xfff0000000000001L0xffffffffffffffffL 的任何值,则结果是 NaN

另外,NaN在 API 内部逻辑上进行处理。


文档

/** 
 * A constant holding a Not-a-Number (NaN) value of type
 * {@code double}. It is equivalent to the value returned by
 * {@code Double.longBitsToDouble(0x7ff8000000000000L)}.
 */
public static final double NaN = 0.0d / 0.0;

顺便提一下,你的代码样例中会测试NaN

/**
 * Returns {@code true} if the specified number is a
 * Not-a-Number (NaN) value, {@code false} otherwise.
 *
 * @param   v   the value to be tested.
 * @return  {@code true} if the value of the argument is NaN;
 *          {@code false} otherwise.
 */
static public boolean isNaN(double v) {
    return (v != v);
}

解决方案

你可以使用 compare/compareTo

Double.NaN 被此方法视为等于其本身且大于所有其他的 double 值(包括 Double.POSITIVE_INFINITY)。

Double.compare(Double.NaN, Double.NaN);
Double.NaN.compareTo(Double.NaN);

或者,equals

如果thisargument都代表Double.NaN,则equals方法返回true,即使Double.NaN == Double.NaN的值为false

Double.NaN.equals(Double.NaN);

你知道有没有任何情况,使得 NaN != NaN 为假会比 NaN != NaN 为真更加复杂吗?我知道IEEE已经做出了这个决定很久了,但从实际角度来看,我从未见过有用的情况。如果一个操作应该运行直到连续迭代产生相同的结果,如果没有这种行为,那么两个连续的迭代产生NaN将被“自然地”检测为退出条件。 - supercat
@falsarella:问题不在于两个随机数是否应被视为“绝对相等”,而在于在什么情况下将任何数字比较为“绝对不相等”对于我们有用。如果我们试图计算f(f(f...f(x)))的极限,并且找到某个n使得y=f[n](x),并且f(y)的结果与y无法区分,则y将与任何更深层次的f(f(f(...f(y)))的结果无法区分。即使我们希望NaN==NaN为false,让Nan!=Nan也为false比让某些x的x!=x为true更少“令人惊讶”。 - supercat
@supercat 如果你正在使用 Double(而不是 double),并且你使用 == 条件语句(不使用 equals 也很奇怪),这意味着当两边相等时,你愿意执行内部块(显然),并且可能会使用 tem(例如,在方程中)。因此,对我来说,最不“令人惊讶”的情况是当 NaN = NaN 时进入我的块,并导致一个令人惊讶的错误!我没有为你展示,可能只有少数人遇到过这个问题。这是一个 OCPJP 的问题,重点是澄清和解决 OP 的问题。 - falsarella
1
@falsarella:我认为Double.NaN的类型不是Double,而是double,所以这个问题与double的行为有关。虽然存在可以测试涉及double值的等价关系的函数,但我知道的唯一令人信服的答案是“因为IEEE的一些人认为相等性测试不应该定义一个等价关系”。顺便问一下,有没有简洁的惯用方法只使用原始运算符来测试xy的等价性?我知道的所有公式都相当笨重。 - supercat
1
最好和简单的答案。谢谢。 - Tarun Nagpal
显示剩余9条评论

19

这可能不是对问题的直接回答。但如果您想检查某个值是否等于Double.NaN,您应该使用以下代码:

double d = Double.NaN
Double.isNaN(d);

这将返回 true


6
Double.NaN的javadoc已经说明了一切:

一个常量,它持有double类型的Not-a-Number(NaN)值。 它相当于通过Double.longBitsToDouble(0x7ff8000000000000L)返回的值。

有趣的是,Double的源代码如下定义了NaN
public static final double NaN = 0.0d / 0.0;

您所描述的特殊行为已经硬编码在JVM中。


6
这个功能是在JVM中硬编码实现的,还是像Peter提到的那样由CPU实现的? - Naweed Chougle

4
根据IEEE浮点算术的双精度数标准,IEEE双精度浮点标准表示需要64位字,可以从左到右编号为0至63。其中,enter image description here
S: Sign – 1 bit
E: Exponent – 11 bits
F: Fraction – 52 bits 

如果E=2047(所有E都是1),且F非零,则V=NaN(“不是一个数字”)。
这意味着,如果所有E位都是1,并且F中有任何非零位,则该数为NaN
因此,除其他外,以下所有数字都是NaN
0 11111111 0000000000000000010000000000000000000000000000000000 = NaN
1 11111111 0000010000000000010001000000000000001000000000000000 = NaN
1 11111111 0000010000011000010001000000000000001000000000000000 = NaN

特别是,您无法进行测试

if (x == Double.NaN) 

要检查特定结果是否等于Double.NaN,因为所有“非数字”值都被视为不同。但是,您可以使用Double.isNaN方法:

if (Double.isNaN(x)) // check whether x is "not a number"

3

NaN是一个特殊的值,表示“不是数字”,它是某些无效算术操作的结果,例如sqrt(-1),并且具有(有时很烦人的)属性,即NaN != NaN


2

NaN表示操作的结果无法用数字表示。最著名的操作是0/0,其结果未知。

因此,NaN与任何东西(包括其他非数字值)都不相等。要了解更多信息,请查看维基百科页面:http://en.wikipedia.org/wiki/NaN


-1:它不代表0/0的结果。0/0始终为NaN,但是其他操作的结果可能是NaN,例如2+NaN没有数学明确结果的操作会产生NaN,如@AdrianMitev的答案所述。 - ANeves
实际上,NaN代表“不是数字”,它是所有操作的结果,其结果为未定义或无法表示的值。最著名和常见的操作是0/0,但显然还有许多其他操作具有相同的结果。我同意我的答案可以改进,但我不同意-1...我刚刚检查了维基百科,发现0/0操作也被用作具有NaN结果的第一个操作示例(http://en.wikipedia.org/wiki/NaN)。 - Matteo
此外,这是Double类的Java源代码:public static final double NaN = 0.0d / 0.0; - Guillaume
1
@Matteo,现在虚假陈述已经消失了。我的-1或+1并不是让你同意或不同意;但是留下一个-1的评论是很好的,这样作者就可以理解为什么他的答案被认为是无用的,并且如果他愿意的话,可以进行更改。 - ANeves
@Guillaume 如果那条评论是针对我的,请重新表达一下:我不理解它。 - ANeves
@ANeves 我同意目标是给出最好的答案。然而,我认为有几种方式来解释同一件事情。此外,我认为初学者可以利用一个简单的现象例子,然后他们可以重新加工它。 - Matteo

0
根据这个链接,它有各种情况和难以记忆。这是我如何记住和区分它们的方法。NaN表示"数学上未定义",例如:"0除以0的结果未定义",因为它是未定义的,所以"与未定义相关的比较当然是未定义的"。此外,它更像是数学前提。另一方面,正无穷和负无穷都是预定义和明确的,例如"正或负无穷大在数学上是明确定义的"。

0

如果你有变量

Double a = Double.NaN

使用

String.valueOf(Double.NaN) == a.toString()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接