为什么NaN不等于NaN?

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

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

IEEE的标准是经过深思熟虑的,所以我相信有一个很好的理由,为什么NaN比较自己会是不好的。 我只是无法弄清楚它是什么。

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

NaN被设计为在所有计算中传播,像感染病毒一样感染它们,所以如果你在NaN上进行深入复杂的计算,就不会冒出一个看似合理的答案。 否则,NaN / NaN应该等于1,连同所有其他的后果,如(NaN / NaN)== 1,(NaN * 1)== NaN等等。如果你想象你的计算在某个地方出错了零分母,产生NaN)等,那么你可能得到疯狂的不正确的(或更糟糕的:微妙的不正确的)从你的计算结果没有明显的指标,为什么。

NaNs在计算数学函数的值时也有很好的理由。 链接文档中给出的例子之一是找到函数f()的零()。 完全可能的是,在用猜测值探测函数的过程中,你将探索函数f()不产生明显结果的地方。 这允许零()看到NaN并继续其工作。

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

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

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

“浮点老人专访”

“IEEE浮点格式的历史”

每一个计算机科学家应该知道什么是浮点运算

那么, log(-1)给出了NaN ,而acos(2)也给出了NaN 。 这是否意味着log(-1) == acos(2) ? 显然不是。 因此, NaN不等于自己是完全合理的。

差不多两年后重新审视,这是一个“NaN安全”的比较函数:

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

我原来的答案(从4年前开始)批评了从现代角度来看的决定,却不了解作出决定的背景。 因此,它不回答这个问题。

这里给出了正确的答案:

NaN != NaN源自两个实用的考虑因素:

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

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

如果委员会预见到未来的使用案例,并认为它们足够重要,那么他们本可以用更复杂的!(x<x & x>x)代替x!=x作为NaN一个测试。 然而,他们的重点更加务实和狭隘:为数字计算提供了最好的解决方案,因此他们的方法没有问题。

===

原始答案:

我很抱歉,就像我赞赏进入最高票的答案的想法一样,我不同意这一点。 NaN并不意味着“未定义” – 请参阅~wkahan/ieee754status/IEEE754.PDF ,第7页(搜索单词“undefined”)。 正如该文件所证实的那样,NaN是一个明确的概念。

此外,IEEE的做法是尽可能遵循常规的数学规则,如果不能遵循“最小惊喜”规则 – 请参阅https://stackoverflow.com/a/1573715/336527 。 任何数学对象本身都是相等的,所以数学规则意味着NaN == NaN应该是真的。 我看不出任何有效和有力的理由偏离这样一个重要的数学原理(更不用说比较三分法等较不重要的规则等)。

因此,我的结论如下。

IEEE委员会成员没有十分清楚地思考这个问题,并犯了一个错误。 由于很少有人理解IEEE委员会的方法,或者关心NaN的标准究竟是怎么说的(也就是说,大部分编译器对NaN的处理都违反了IEEE标准),所以没有人提出警告。 因此,这个错误现在被嵌入到标准中。 这是不可能被修复的,因为这样的修复会破坏很多现有的代码。

编辑: 这是一个非常翔实的讨论。 注意:为了得到一个公正的看法,你必须阅读整个线程,因为Guido和其他一些核心开发者有不同的看法。 但是,Guido对这个话题并不感兴趣,并且大部分遵循了Tim Peters的建议。 如果任何人有Tim Peters的论点赞成NaN != NaN ,请在评论中加入他们; 他们有很好的机会改变我的意见。

一个不错的属性是:如果x == x返回false,那么xNaN.

(可以使用这个属性来检查x是否是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的方式就是告诉你什么是不是什么东西,而不是什么东西。 例如,如果我说“一个苹果不是大猩猩”,“一个桔子不是大猩猩”,你会得出结论:“一个苹果”是一个橘子吗?

实际上,数学中有一个被称为“统一”价值的概念。 这些值是经过认真构建的扩展,以调和系统中的外部问题。 例如,你可以把复杂的平面上的无限环看作是一个点或一组点,有些以前自命不凡的问题就会消失。 还有其他一些关于基数的例子,你可以证明,只要| P(A)| > | A | 没有什么可以打破

免责声明:我只是在我的数学研究过程中模糊记忆我的一些有趣的警告。 我很抱歉,如果我做了一个可悲的工作,代表我所提到的概念。

如果你想相信NaN是一个孤立的价值,那么你可能会对平等运营商的一些结果感到不满,如你所期望/想要的那样。 然而,如果你选择相信NaN更多的是由单独的占位符表示的“不良”的连续体,那么你对平等运算符的行为完全满意。 换句话说,你看不到你在海中捕捉到的鱼,但你捕捉到另一个看起来一样的,但同样臭的鱼。