152

相关的 IEEE 标准定义了一个数字常量 NaN(不是数字),并规定 NaN 应该与自身不相等。这是为什么?

我熟悉的所有语言都实现了这个规则。但它经常会导致严重的问题,例如当 NaN 存储在容器中时的意外行为,当 NaN 在正在排序的数据中时等等。更不用说,绝大多数程序员都希望任何对象都等于自身(在他们了解 NaN 之前),因此让他们感到惊讶增加了错误和混乱。

IEEE 标准经过深思熟虑,所以我确信 NaN 与自身比较是不好的,这是有充分理由的。我只是无法弄清楚它是什么。

编辑:请参阅IEEE754 NaN 值的所有比较返回 false 的理由是什么?作为权威答案。

4

6 回答 6

183

接受的答案是 100% 没有问题 WRONG。没有半点错误,甚至一点点错误。我担心当这个问题在搜索中弹出时,这个问题会在很长一段时间内混淆和误导程序员。

NaN 旨在通过所有计算传播,像病毒一样感染它们,所以如果在你深入复杂的计算中某个地方遇到 NaN,你就不会冒出一个看似明智的答案。否则,通过恒等式 NaN/NaN 应等于 1,以及所有其他结果,例如 (NaN/NaN)==1、(NaN*1)==NaN 等。如果您认为您的计算在某处出错(四舍五入产生了零分母,产生 NaN) 等,那么你可能会从你的计算中得到非常不正确(或更糟糕:略微不正确)的结果,而没有明显的指示说明原因。

在探索数学函数的值时,NaN 在计算中也有很好的理由;链接文档中给出的示例之一是查找函数 f() 的 zeros()。完全有可能在用猜测值探测函数的过程中,你将探测一个函数 f() 没有产生合理结果的函数。这允许 zeros() 查看 NaN 并继续其工作。

NaN 的替代方法是在遇到非法操作(也称为信号或陷阱)时立即触发异常。除了您可能会遇到巨大的性能损失外,当时无法保证 CPU 会在硬件中支持它,或者操作系统/语言会在软件中支持它;每个人在处理浮点时都是自己独特的雪花。IEEE 决定在软件中将其显式处理为 NaN 值,以便它可以跨任何操作系统或编程语言移植。正确的浮点算法通常在所有浮点实现中都是正确的,无论是 node.js 还是 COBOL(哈哈)。

理论上,您不必设置特定的#pragma 指令、设置疯狂的编译器标志、捕获正确的异常或安装特殊的信号处理程序来使看似相同的算法实际正常工作。不幸的是,一些语言设计者和编译器编写者一直忙于尽其所能撤消此功能。

请阅读有关 IEEE 754 浮点历史的一些信息。这也是对委员会成员回答的类似问题的回答:对于 IEEE754 NaN 值的所有比较返回 false 的理由是什么?

《专访浮点老人》

《IEEE 浮点格式的历史》

每个计算机科学家都应该知道的浮点运算知识

于 2014-05-14T23:06:12.737 回答
116

嗯,log(-1)给了NaNacos(2)也给了NaN。这是否意味着log(-1) == acos(2)?显然不是。NaN因此,不等于自身是完全有道理的。

差不多两年后重新审视这个,这里有一个“NaN-safe”比较函数:

function compare(a,b) {
    return a == b || (isNaN(a) && isNaN(b));
}
于 2012-04-05T18:45:18.907 回答
34

我最初的回答(从 4 年前开始)从现代的角度批评了这个决定,而不了解做出决定的背景。因此,它没有回答这个问题。

正确答案在这里给出:

NaN!=NaN源于两个务实的考虑:

isnan( )[...] NaN 在 8087 算术中形式化时没有谓词;有必要为程序员提供一种方便有效的检测 NaN 值的方法,这种方法不依赖于isnan( )可能需要很多年的编程语言

这种方法有一个缺点:它使 NaN 在许多与数值计算无关的情况下不太有用。例如,很久以后,当人们想用它NaN来表示缺失值并将它们放入基于散列的容器中时,他们就做不到了。

如果委员会预见到未来的用例,并认为它们足够重要,那么他们可能会选择更冗长的,!(x<x & x>x)而不是x!=x作为NaN. 然而,他们的关注点更加务实和狭隘:为数值计算提供最佳解决方案,因此他们认为他们的方法没有问题。

===

原答案:

很抱歉,尽管我很欣赏票数最高的答案中的想法,但我不同意。NaN 并不意味着“未定义” - 请参阅http://www.cs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF第 7 页(搜索“未定义”一词)。正如该文件所证实的,NaN 是一个定义明确的概念。

此外,IEEE 的方法是尽可能遵循常规数学规则,如果不能,则遵循“最小意外”规则 - 参见https://stackoverflow.com/a/1573715/336527。任何数学对象都等于它自己,所以数学规则意味着 NaN == NaN 应该是 True。我看不出有任何有效和有力的理由来偏离这样一个主要的数学原理(更不用说不太重要的比较三分法规则等)。

结果,我的结论如下。

IEEE 委员会成员对此并没有想得很清楚,并且犯了一个错误。由于很少有人了解 IEEE 委员会的方法,或者关心标准对 NaN 的确切说明(也就是说:大多数编译器对 NaN 的处理无论如何都违反了 IEEE 标准),所以没有人发出警报。因此,这个错误现在嵌入到标准中。它不太可能被修复,因为这样的修复会破坏很多现有代码。

编辑:这是一个非常有用的讨论的帖子。注意:要获得公正的观点,您必须阅读整个线程,因为 Guido 与其他一些核心开发人员的观点不同。然而,Guido 个人对这个话题并不感兴趣,并且在很大程度上遵循了 Tim Peters 的建议。如果有人支持 Tim Peters 的论点NaN != NaN,请在评论中添加它们;他们很有机会改变我的看法。

于 2012-04-08T01:50:42.900 回答
13

一个不错的属性是:如果x == x返回 false,x则为NaN.

(可以使用此属性来检查是否x存在NaN。)

于 2012-04-05T18:47:27.867 回答
9

试试这个:

var a = 'asdf';
var b = null;

var intA = parseInt(a);
var intB = parseInt(b);

console.log(intA); //logs NaN
console.log(intB); //logs NaN
console.log(intA==intB);// logs false

如果 intA == intB 为真,那可能会导致您得出结论 a==b,但显然不是。

另一种看待它的方式是,NaN 只是为您提供有关不是什么东西的信息,而不是它是什么。例如,如果我说“苹果不是大猩猩”和“橙子不是大猩猩”,你会得出结论“苹果”==“橙子”吗?

于 2013-04-02T12:43:06.867 回答
2

实际上,数学中有一个概念被称为“统一”值。这些值是经过精心构建的扩展,用于协调系统中的外围问题。例如,您可以将复平面中的无限远环视为一个点或一组点,而一些以前自命不凡的问题就消失了。还有其他关于集合基数的例子,你可以证明你可以选择无限连续统的结构,只要 |P(A)| > |一个| 什么都没有。

免责声明:我只是根据我对数学学习期间一些有趣的警告的模糊记忆来工作。如果我在代表我上面提到的概念方面做得很糟糕,我深表歉意。

如果您想相信 NaN 是一个单独的值,那么您可能会对某些结果感到不满,例如等式运算符没有按照您期望/想要的方式工作。但是,如果您选择相信 NaN 更像是由一个单独的占位符表示的“坏”的连续统一体,那么您对等式运算符的行为非常满意。换句话说,你看不到你在海里钓到的鱼,但你钓到了另一条看起来一样但同样臭的鱼。

于 2013-03-19T18:54:26.340 回答