为什么 NaN 不等于 NaN?

相关的 IEEE 标准定义了一个数值常量 NaN (而不是一个数字) ,并规定 NaN 应该与其本身进行比较。为什么?

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

IEEE 标准是经过深思熟虑的,所以我确信 NaN 与它本身相等是不好的,这是有充分理由的。我只是不知道那是什么。

编辑: 请参考 对于 IEEE754 NaN 值返回 false 的所有比较的基本原理是什么?作为权威答案。

62219 次浏览

log(-1)给出 NaN acos(2)也给出 NaN。那是 log(-1) == acos(2)吗?显然不是。因此,NaN不等于它自己是非常有意义的。

将近两年后,我们再次回顾这个问题,下面是一个“ NaN 安全”比较函数:

function compare(a,b) {
return a == b || (isNaN(a) && isNaN(b));
}

一个很好的属性是: 如果 x == x返回 false,那么 x就是 NaN.

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

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

正确答案是 给你:

NaN! = NaN源于两个实用考虑:

在 NaN 被8087算法正式化的时候,还没有 isnan( )谓词; 有必要为程序员提供一种方便有效的检测 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 的建议。如果有人对蒂姆 · 彼得斯的观点支持 NaN != NaN,请在评论中加上他们,他们很有可能改变我的观点。

事实上,在数学中有一个概念被称为“统一”价值观。这些值是精心构造的扩展,用于协调系统中的异常问题。例如,你可以把复平面上的无穷远处的环想象成一个点或一组点,一些以前自命不凡的问题就消失了。关于集合的基数还有其他的例子,在这些例子中,你可以证明,只要 | P (A) | > | A | 没有任何破坏,你就可以选择无穷大连续体的结构。

免责声明: 我只是在模糊的记忆中工作,我的一些有趣的警告在我的数学学习。如果我在表达上面提到的概念方面做得很糟糕,我道歉。

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

试试这个:

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 只是给你关于什么东西不是什么的信息,而不是它是什么。例如,如果我说“一个苹果不是大猩猩”和“一个橙子不是大猩猩”,你会得出“一个苹果”= = “一个橙子”的结论吗?

接受的答案是100% 没有问题错误。没有半点错误,甚至没有一点错误。我担心当这个问题在搜索中出现时,这个问题会长时间地困扰和误导程序员。

NaN 被设计用来传播所有的计算,像病毒一样感染它们,所以如果在深入复杂的计算中偶然发现一个 NaN,就不会冒出一个看似合理的答案。否则,根据身份 NaN/NaN 应该等于1,以及所有其他结果,如(NaN/NaN) = = 1,(NaN * 1) = = NaN,等等。如果你认为你的计算出了问题(舍入产生了零分母,产生了 NaN)等等,那么你可能从你的计算中得到非常不正确的结果(或者更糟糕: 微妙的错误) ,而没有明显的指标来说明为什么。

在计算中探测数学函数的值时使用 NaNs 也有很好的理由; 在链接文档中给出的一个例子是找到函数 f ()的零点()。在使用猜测值探测函数的过程中,完全有可能在函数 f ()不产生任何有意义结果的情况下进行探测。这允许 zero ()查看 NaN 并继续其工作。

NaN 的替代方法是在遇到非法操作(也称为信号或陷阱)时立即触发异常。除了你可能会遇到的巨大的性能损失,当时并不能保证 CPU 会在硬件上支持它,或者操作系统/语言会在软件上支持它; 每个人在处理浮点数方面都是独一无二的。IEEE 决定在软件中以 NaN 值的形式明确地处理它,这样它就可以跨任何操作系统或编程语言移植。正确的浮点算法通常在所有浮点实现中都是正确的,不管是 node.js 还是 COBOL (哈)。

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

请阅读有关 IEEE754浮点数历史的一些信息。还有一个类似问题的答案,委员会的一名成员作了答复: 对于 IEEE754 NaN 值返回 false 的所有比较的基本原理是什么?

《浮点老人访谈录》

IEEE 浮点格式的历史

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