【问题标题】:Why is NaN not equal to NaN? [duplicate]为什么 NaN 不等于 NaN? [复制]
【发布时间】:2012-04-19 12:29:28
【问题描述】:

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

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

IEEE 标准经过深思熟虑,因此我确信 NaN 与自身进行比较是不好的,这是有充分理由的。我就是不知道是什么。

编辑:请参考What is the rationale for all comparisons returning false for IEEE754 NaN values?作为权威答案。

【问题讨论】:

  • IEEE 标准是由工程师设计的,而不是程序员、计算机供应商或数学库的作者,NaN 规则对他们来说是一场灾难。

标签: floating-point language-agnostic nan ieee-754


【解决方案1】:

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

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

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

【讨论】:

  • 好吧,如果您正在寻找log 函数和acos 函数之间的交集,那么所有超过-1 的负值都将被视为一个交集。有趣的是,Infinity == Infinity 是真的,尽管在实际数学中不能这么说。
  • 考虑到 Inf == Inf,并且考虑到人们可能很容易争辩说一个对象应该等于它自己,我怀疑 IEEE 背后还有其他一些非常具体且非常有力的基本原理选择...
  • 1 + 3 = 42 + 2 = 4 。这是否意味着 1 + 3 = 2 + 2 ?显然是的。因此,您的回答并不完全合理。
  • 但是log(-1) != log(-1) 没有意义。因此,NaN 不等于 NaNNaN 不等于 NaN 在所有情况下都是有意义的。可以说,如果 NaN == NaN 被评估为代表未知的东西会更有意义,但是 == 不会返回布尔值。
  • 如果您提供两个不相等的不同数字,您的 NaN 安全比较函数将返回 true。类似于 return a == b || (isNaN(a) && isNaN(b)) 应该可以吗?
【解决方案2】:

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

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

【讨论】:

  • 一个人可以拥有该属性但仍然有 (Nan != Nan) also 返回 false。如果 IEEE 这样做了,想要测试 ab 之间的等价关系的代码可以使用 !(a != b)
  • 这是 np.isnan() 和 pd.isnull() 的绝佳替代品! !
【解决方案3】:

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

给出正确答案here

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

[...] 在 8087 算术中将 NaN 形式化时,没有 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 应该为真。我看不出有任何有效和有力的理由来偏离这样一个主要的数学原理(更不用说不太重要的比较三分法规则等)。

因此,我的结论如下。

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

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

【讨论】:

  • 恕我直言,让NaN 违反三分法是有道理的,但是像你一样,当== 的操作数都是相同类型时,我认为没有合理的语义理由来定义等价关系(去更进一步,我认为语言应该明确禁止不同类型的事物之间的比较——即使存在隐式转换——如果这样的比较不能实现等价关系)。等价关系的概念在编程和数学中都非常重要,违反它似乎很疯狂。
  • @EamonNerbonne: 让NaN==NaN 返回非真或假的值会有问题,但鉴于(a<b) 不一定等于!(a>=b),我认为(a==b) 没有理由必须必然等于!(a!=b)。让 NaN==NaNNan!=NaN 都返回 false 将允许需要定义相等性的代码使用它需要的定义。
  • 这个答案是错误的,错误的!请参阅下面的答案。
  • 我不知道有任何公理或假设表明数学对象(您甚至如何定义数学对象???)必须等于自身。
  • 即使你基于集合 S 上的恒等函数 f,其中 f(x) = x,我认为 NaN 不是数字集合的一部分,毕竟,它实际上不是数字。所以我没有看到任何来自标识函数的论点 NaN 应该等于它自己。
【解决方案4】:

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

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

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

【讨论】:

  • 是的,在数学中您可以添加 infinity 和类似的值。但是,它们永远不会破坏对等关系。程序员的平等represents an equivalence relation in math,顾名思义是自反的。一个糟糕的程序员可以定义==,它不是自反的、对称的和传递的;不幸的是Python不会阻止他。但是,当 Python 本身使 == 成为非自反的,并且您甚至无法覆盖它时,无论从实际角度(容器成员资格)还是优雅/精神清晰的角度来看,这都是一场彻底的灾难
【解决方案5】:

试试这个:

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 只是为您提供有关“不是”的信息,而不是它是什么。例如,如果我说“苹果不是大猩猩”和“橙子不是大猩猩”,你会得出结论“苹果”==“橙子”吗?

【讨论】:

  • "这可能会让你得出 a==b 的结论" -- 但这只是一个无效的结论 -- 例如 strtol("010") == strtol("8") .
  • 我不遵循你的逻辑。给定a=16777216fb=0.25c=0.125,是否应该将a+b == a+c 的事实视为暗示b==c?或者仅仅是这两个计算产生无法区分的结果?为什么不应该认为 sqrt(-1) 和 (0.0/0.0) 无法区分,没有区分它们的方法?
  • 如果你暗示无法区分的事物应该被认为是平等的,我不同意。平等意味着你确实有办法区分两个比较对象,而不仅仅是对它们缺乏同样的了解。如果您无法区分它们,那么它们可能相等,也可能不相等。我可以看到 NaN==NaN 返回“未定义”,但不是真的。
  • @MikeC 几乎没有太多语法就找到了原因
  • 这么多答案,我只能理解你的解释,赞!!
【解决方案6】:

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

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 浮点历史的信息。这也是关于委员会成员回答的类似问题的答案:What is the rationale for all comparisons returning false for IEEE754 NaN values?

"An Interview with the Old Man of Floating-Point"

"History of IEEE Floating-Point Format"

What every computer scientist should know about floating point arithmetic

【讨论】:

  • 我也喜欢 NaN“像病毒一样”传播。不幸的是,事实并非如此。例如,当您比较NaN + 1 != 0NaN * 1 > 0 时,它会返回TrueFalse,就好像一切正​​常。因此,如果您打算使用比较运算符,则不能依靠NaN 来保护您免受问题的影响。鉴于比较不会帮助您“传播”NaN,为什么不至少让它们变得有意义呢?就目前情况而言,它们在字典中弄乱了 NaN 的用例,它们使排序不稳定等。此外,您的答案中还有一个小错误。如果我按照自己的方式,NaN/NaN == 1 不会评估 True
  • 另外,您声称我的回答 100% 肯定是绝对错误的。但是,您引用的 IEEE 委员会的人实际上在您引用的帖子中说:`许多评论者认为,保留平等和三分法的自反性会更有用,因为采用 NaN != NaN 不会似乎保留了任何熟悉的公理。我承认对这种观点有一些同情,所以我想我会重新审视这个答案并提供更多背景信息。`所以,亲爱的先生,你可能会考虑在你的陈述中不那么有力。
  • 我从来没有说过设计不是故意的。以糟糕的逻辑或对问题的理解不足为指导的深思熟虑的设计仍然是一个错误。但这种讨论毫无意义。你显然拥有终极真理的知识,你的工作就是向像我这样没有受过教育的大众宣讲它。享受圣职。
  • 通过计算传播 NaN 与与 NaN 的相等比较完全无关。可移植性和将 NaN 实现为位模式对于 NaN 是否应该与自身比较的问题也无关紧要。事实上,我在这个答案中找不到 NaN != NaN 的任何理由,除了底部的第一个链接答案,这解释了原因是当时 isnan() 不可用,这是为什么作出了决定。然而,我看不出任何今天仍然有效的理由,除了改变语义是一个非常糟糕的主意。
  • @xenadu 我可以看到 log(-1) == acos(2) 提供了一些支持当前行为的论据。但是,您注意到自己无论如何都不应该比较浮点数是否相等,所以这是一个弱论点(并且有很多理由决定另一种方式)。但是,这不是我之前评论的重点。我的观点是,上面的答案虽然正确,但没有给出任何理由说明 NaN 不应与自身比较。你所说的一切都与那个问题完全无关。
猜你喜欢
  • 2020-03-12
  • 2013-11-26
  • 2016-05-09
  • 2021-04-29
相关资源
最近更新 更多