为啥 Double.NaN==Double.NaN 返回 false?

Posted

技术标签:

【中文标题】为啥 Double.NaN==Double.NaN 返回 false?【英文标题】:Why does Double.NaN==Double.NaN return false?为什么 Double.NaN==Double.NaN 返回 false? 【发布时间】:2012-02-07 20:04:32 【问题描述】:

我只是在研究 OCPJP 问题,发现了这个奇怪的代码:

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

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

false
true

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

【问题讨论】:

这真的很奇怪。因为 Double.NaN 是静态 final,所以与 == 的比较应该返回 true。为问题 +1。 python中也是这样:In [1]: NaN==NaN Out[1]: False 正确遵循 IEEE 754 标准的所有语言都是如此。 直觉:“Hello”不是数字,true(布尔)也不是数字。 NaN != NaN 出于同样的原因 "Hello" != true @Stephan:如果Double.NaN 的类型为java.lang.Double,则与Double.NaN==Double.NaN 的比较确实应该返回true。但是,它的类型是原语 double,并且适用于 double 的运算符规则(要求这种不等式以符合 IEEE 754,如答案中所述)。 【参考方案1】:

NaN 是一个特殊值,表示“不是数字”;它是某些无效算术运算的结果,例如sqrt(-1),并且具有NaN != NaN 的(有时令人讨厌的)属性。

【讨论】:

【参考方案2】:

NaN 的意思是“不是数字”。

Java Language Specification (JLS) Third Edition says:

上溢的运算产生有符号无穷大,下溢的运算产生非规格化值或有符号零,而没有数学确定结果的运算产生 NaN。所有以 NaN 作为操作数的数值运算都会产生 NaN 作为结果。如前所述,NaN 是无序的,因此涉及一个或两个 NaN 的数值比较操作会返回 false,而涉及 NaN 的任何 != 比较都会返回 true,包括 x!=xx 为 NaN 时。

【讨论】:

@nibot:基本正确。任何与符合 IEEE 的浮点数的比较都会产生 false。因此,该标准与 Java 的不同之处在于 IEEE 要求 (NAN != NAN) == false 打开这个潘多拉魔盒 - 你在哪里看到“IEEE 要求 (NAN != NAN) == false”?【参考方案3】:

javadoc for Double.NaN 说明了一切:

一个保持double 类型的非数字 (NaN) 值的常量。相当于Double.longBitsToDouble(0x7ff8000000000000L)返回的值。

有趣的是,Double 的来源定义了NaN

public static final double NaN = 0.0d / 0.0;

您描述的特殊行为是硬连线到 JVM 中的。

【讨论】:

是在 JVM 中硬连线,还是如 Peter 所说的那样由 CPU 实现?【参考方案4】:

Not a number 表示操作的结果,其结果不能用数字表示。最著名的操作是 0/0,其结果未知。

因此,NaN 不等于任何值(包括其他非数字值)。欲了解更多信息,请查看***页面:http://en.wikipedia.org/wiki/NaN

【讨论】:

-1:它确实代表0/0的结果。 0/0 始终是 NaN,但 NaN 可以是其他操作的结果 - 例如 2+NaN: an operation that has no mathematically definite result produces NaN,根据 @AdrianMitev 的回答 确实,NaN 代表“非数字”,它是所有具有未定义或不可表示值的操作的结果。最著名和最常见的操作是 0/0,但显然还有大量其他操作具有相同的结果。我同意我的答案可以改进,但我不同意 -1... 我刚刚检查过***也使用 0/0 操作作为第一个操作示例,其中包含 NaN 结果 (en.wikipedia.org/wiki/NaN)。 另外,这是 Double 的 Java 源代码:public static final double NaN = 0.0d / 0.0; @Matteo +0,现在错误的声明已经消失了。我的 -1 或 +1 不是让你同意或不同意的;但最好留下带有-1 的评论,以便作者能够理解为什么他的答案被认为是无用的 - 如果他愿意,可以更改它。 @Guillaume 如果该评论是针对我的,请改写:我不明白。【参考方案5】:

为什么会有这种逻辑

NaN 表示Not a Number。什么不是数字?任何事物。您可以在一侧拥有任何东西,而在另一侧拥有任何东西,因此无法保证两者是平等的。 NaN 是用 Double.longBitsToDouble(0x7ff8000000000000L) 计算的,正如您在 longBitsToDouble 的文档中看到的那样:

如果参数是0x7ff0000000000001L 到范围内的任何值 0x7fffffffffffffffL 或范围 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 你怎么能说两个随机非数自然相等?或者说,原始平等?将 NaN 视为一个实例,而不是原始的东西。每个不同的异常结果都是奇怪事物的不同实例,即使两者应该表示相同,对不同的实例使用 == 也必须返回 false。另一方面,当使用 equals 时,可以按照您的意愿正确处理。 [docs.oracle.com/javase/7/docs/api/java/lang/… @falsarella:问题不在于两个随机数是否应该被视为“绝对相等”,而是在什么情况下让任何数字与自身“绝对不相等”比较有用。如果一个人试图计算f(f(f...f(x))) 的极限,并且发现一些ny=f[n](x) 使得f(y) 的结果与y 无法区分,那么y 将与结果无法区分任何嵌套更深的f(f(f(...f(y)))。即使有人希望NaN==NaN 为假,对于某些x,让Nan!=Nan also 为假不会比让x!=x 为真更“令人惊讶”。 @falsarella:我认为Double.NaN的类型不是Double,而是double,所以这个问题与double的行为有关。尽管存在可以测试涉及double 值的等价关系的函数,但我所知道的“为什么”(这是原始问题的一部分)的唯一令人信服的答案是“因为 IEEE 的一些人不认为平等-测试应该定义一个等价关系”。顺便说一句,是否有任何简洁的惯用方法来测试 xy 是否仅使用原始运算符进行等效?我所知道的所有配方都相当笨重。 最佳而简单的答案。谢谢【参考方案6】:

根据定义,NaN 不等于包括 NaN 在内的任何数字。这是 IEEE 754 标准的一部分,由 CPU/FPU 实现。这不是 JVM 必须添加任何逻辑来支持的东西。

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

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

Java 将所有 NaN 视为安静的 NaN。

【讨论】:

它是由 CPU 实现的,还是像 Bohemian 提到的那样在 JVM 中硬连线? JVM 必须调用能正确实现它的任何东西。在 PC 上,CPU 完成所有工作。在没有这种支持的机器上,JVM 必须实现它。 (我不知道任何这样的机器) 在选择 8087 的那一天,C 库包含一个 FP 仿真器。无论哪种方式,像 JVM 这样的程序都不必担心。【参考方案7】:

按照 The IEEE standard for floating point arithmetic 表示双精度数字,

IEEE 双精度浮点标准表示 需要一个 64 位的字,可以表示为从 0 到 63,从左到右

哪里,

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"

【讨论】:

【参考方案8】:

这可能不是问题的直接答案。 但是如果你想检查某个东西是否等于Double.NaN,你应该使用这个:

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

这将返回true

【讨论】:

【参考方案9】:

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

【讨论】:

【参考方案10】:

如果你有变量

Double a = Double.NaN

使用

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

【讨论】:

以上是关于为啥 Double.NaN==Double.NaN 返回 false?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Assert.AreEqual(1.0, double.NaN, 1.0) 通过?

与 NaN 不同,为啥浮点无穷大是相等的?

double.NaN 和 double.NegativeInfinity 的 CompareTo 行为

与 Double.NaN 相等

将 Double.NaN 与自身进行比较

double.NaN - 这个违反直觉的功能是如何工作的?