为啥 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!=x
当x
为 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
或范围0xfff0000000000001L
到0xffffffffffffffffL
,结果是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
:
如果
this
和argument
都代表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)))
的极限,并且发现一些n
的y=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 的一些人不认为平等-测试应该定义一个等价关系”。顺便说一句,是否有任何简洁的惯用方法来测试 x
和 y
是否仅使用原始运算符进行等效?我所知道的所有配方都相当笨重。
最佳而简单的答案。谢谢【参考方案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) 通过?