为什么NaN不等于NaN?

max*_*max 114 language-agnostic floating-point nan ieee-754

相关的IEEE标准定义了一个数字常量NaN(不是数字),并规定NaN应该比较为不等于它自己.这是为什么?

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

IEEE标准经过深思熟虑,因此我确信NaN的比较与其本身相同是很糟糕的.我只是想不通它是什么.

rus*_*hop 145

接受的答案是100%毫无疑问错误.不是错误的一半,甚至是错误的错误.我担心这个问题会在搜索中弹出这个问题的时候长时间混淆和误导程序员.

NaN被设计为通过所有计算传播,像病毒一样感染它们,所以如果你在深度,复杂的计算中的某个地方遇到NaN,你就不会冒出一个看似明智的答案.否则通过身份NaN/NaN应该等于1,以及所有其他后果,如(NaN/NaN)== 1,(NaN*1)== NaN等.如果你想象你的计算在某处出错了(舍入产生了一个零分母,产生NaN)等,那么你可能会因为你的计算而得到非常不正确(或者更糟:微妙的错误)的结果而没有关于原因的明显指标.

在探究数学函数的值时,NaNs在计算中也有很好的理由; 链接文档中给出的一个示例是找到函数f()的零().完全可能的是,在使用猜测值探测函数的过程中,您将探测函数f()不会产生明显结果的函数.这允许zeros()查看NaN并继续其工作.

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

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

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

"对浮点老人的采访"

"IEEE浮点格式的历史"

每个计算机科学家应该了解浮点运算

  • 此外,你声称我的答案100%肯定是错误的.然而,你所引用的IEEE委员会的人实际上在你所引用的帖子中说:"许多评论者认为,以采纳NaN为准,保持平等和三分法的反身性更为有用!= NaN不似乎保留了任何熟悉的公理.我承认对这个观点有一些同情,所以我想我会重新审视这个答案,并提供更多的背景.所以,亲爱的先生,你可能会认为你的陈述中的力量要小一些. (28认同)
  • 我也喜欢NaN传播"像病毒一样".不幸的是,事实并非如此.比较的那一刻,例如,"NaN + 1!= 0"或"NaN*1> 0",它返回"True"或"False",好像一切都很好.因此,如果您打算使用比较运算符,则不能依赖于"NaN"来保护您免受问题的影响.鉴于比较不会帮助你"传播"NaN,为什么不至少让它们变得敏感?事实上,他们搞乱了字典中NaN的用例,它们使得排序不稳定等等.另外,你的答案中有一个小错误.如果我走的话,`NaN/NaN == 1`不会评估'真'. (19认同)
  • 通过计算传播NaN与与NaN的平等比较完全无关.可移植性和实现NaN作为一种位模式对于NaN是否应该与其自身进行比较的问题也无关紧要.事实上,我在这个答案中找不到NaN!= NaN的任何理由,除了底部的第一个链接答案,这解释了原因是当时`isnan()`不可用,这是有效的之所以作出决定.但是,我看不出今天仍然有效的任何理由,除了改变语义是一个非常糟糕的主意. (13认同)
  • @xenadu我可以看到log(-1)== acos(2)提供了一些支持当前行为的参数.但是,你注意到自己不应该比较浮点数是否相等,所以这是一个弱论点(并且有很多理由来决定另一种方式).但是,这不是我之前评论的重点.我的观点是,上面的答案虽然正确,但没有说明为什么NaN不应该与自身相等.你谈论的一切都与这个问题完全无关. (6认同)
  • 我从未说过设计不是故意的.由逻辑不良或对问题理解不足引导的故意设计仍然是一个错误.但这种讨论毫无意义.你显然拥有最终真理的知识,你的工作就是把它传给像我这样未受过教育的群众.享受祭司职位. (4认同)
  • 忽略了你的嘲笑,我向你解释了这个决定:当时几乎没有机会统一处理跨语言,编译器,硬件和操作系统的异常/信号.决定将NaN作为一种位模式处理,因此它可以完全移植,因为它在实际计算中很有用.我认为这是正确的决定,你不同意,但决定不是由于逻辑不良或理解不充分. (3认同)
  • 您描述的一些后果并不真实,其余的则偏离主题。例如,“否则,根据恒等式 NaN/NaN 应等于 1。” 该标准本身就违反了这一推理。Infinity == Infinity,但 Infinity / Infinity == NaN,而不是 1。显然,因此 NaN == NaN 和 NaN / NaN == NaN 都是可能的。其次,对于 NaN 来说,语句 (NaN*1)==NaN 必须为真才能“传播到所有计算”,正如您自己所说。这些支持性陈述是错误的,并且您引用的任何来源都没有声明它们。相反,您的第一个消息来源说它是为了满足 isnan(x) = (x != x) (3认同)
  • 我理解,但我希望新开发人员或那些对浮点细节不感兴趣的人 100% 清楚,您关于委员会犯了“错误”的说法是不正确的 - 设计是故意的。 (2认同)
  • @SvenMarnach这不对.NaN意味着答案是未知的.试图比较NaN在语义上是没有意义的.看下面的第二个答案......应该记录(-1)== acos(2)?因为如果NaN == NaN那么他们这样做显然是无稽之谈.如果当时大多数语言,平台和CPU都支持捕获/抛出异常,那么标准可能选择了该路由而不是NaN.他们没有.无论如何,大多数开发人员滥用浮点数,你不应该在大多数时候比较直接平等! (2认同)
  • @xenadu,但将它们比较为不相等也是无效的答案。log(-1) == log(-1) 应该吗?在比较两个 NaN 时,IMO == 和 != 都是不正确的,但为了实用性 == 打破了更少的规则和更少的容器。 (2认同)

Nie*_*sol 101

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

两年后重新审视这一点,这是一个"NaN安全"比较功能:

function compare(a,b) {
    return a == b || (isNaN(a) && isNaN(b));
}
Run Code Online (Sandbox Code Playgroud)

  • 但是`log(-1)!= log(-1)`没有意义.因此,在所有情况下,"NaN"等于"NaN"和"NaN"都不等于"NaN".可以说,如果`NaN == NaN`被评估为代表未知的东西,那么它会更有意义,但是`==`不会返回布尔值. (23认同)
  • 好吧,如果你正在寻找`log`函数和`acos`函数之间的交集,那么超过`-1`的所有负值都将被视为一个交集.有趣的是,"Infinity == Infinity"是真的,尽管在实际数学中不能说同样的事实. (19认同)
  • `1 + 3 = 4`和'2 + 2 = 4`.这是否意味着"1 + 3 = 2 + 2"?显然是的.因此,你的答案并不完美. (12认同)
  • 鉴于Inf == Inf,并且考虑到一个人可能很容易争辩说一个对象应该与自己相等,我怀疑IEEE选择背后还有其他一些非常具体且非常强大的理由...... (8认同)
  • 如果提供两个彼此不相等的不同数字,则NaN安全比较函数将返回true.像返回a == b ||之类的东西 (isNaN(a)&& isNaN(b))应该工作吗? (7认同)
  • @Bergi:我当然不会指望`f(a)= f(b)<=> a = b`这只有在f是一个内射函数时才会成立.但是f可以是不变的.**可以*期望的是'a = b => f(a)= f(b)`,但是你的论证不适用. (3认同)
  • 即使有人认为'NaN == NaN`应该返回false,为什么这意味着'NaN!= NaN`应该返回true?鉴于'NaN <NaN`和'NaN> = NaN`都返回false,即使它们是"相反"的条件,为什么不能'NaN == NaN`和'NaN!= NaN`都返回true? (3认同)
  • @JimBalter:哪些语言会迫使`a==b` 等同于`!(a!=b)`,但不会强迫`a&gt;b` 等同于`!(a&lt;=b)`?此外,您能否提出任何使用“NaN!=NaN”有用的用例,这些用例与检查值是否已存储在集合中一样常见?如果一种语言不能支持两种不同的相等含义,那么指定想要测试两个事物是否相等的非 NaN 代码应该使用 `(x&lt;=y)||(x&gt;=y)` 似乎不那么烦人也就是说,否则类型不可知的等效测试代码必须特殊情况下浮动。 (2认同)
  • 显然,'NaN == NaN`应该返回'NaN`. (2认同)

max*_*max 31

我的原始答案(从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页(搜索"undefined"一词).正如该文件所证实的那样,NaN是一个定义明确的概念.

此外,IEEE方法是尽可能遵循常规数学规则,当它们不能时,遵循"最少惊喜"的规则 - 请参阅/sf/answers/110160081/.任何数学对象都等于它自己,所以数学规则意味着NaN == NaN应该是真的.我看不出任何有效和有力的理由偏离这样一个重要的数学原理(更不用说比较三分法的不太重要的规则等).

结果,我的结论如下.

IEEE委员会成员并没有非常清楚地认为这一点,并犯了一个错误.由于很少有人理解IEEE委员会的做法,或者关心NaN的标准究竟是什么(也就是说:大多数编译器对NaN的处理都违反了IEEE标准),没有人发出警报.因此,这个错误现在已经嵌入到标准中.它不太可能被修复,因为这样的修复会破坏很多现有的代码.

编辑:这是一篇非常翔实的讨论中的一篇文章.注意:要获得无偏见的视图,您必须阅读整个线程,因为Guido对其他一些核心开发人员的看法不同.然而,Guido对这个话题并不感兴趣,并且很大程度上遵循了Tim Peters的建议.如果有人赞成Tim Peters的观点NaN != NaN,请在评论中添加; 他们很有可能改变我的观点.

  • 这个答案错误错误!请参阅下面的答案. (10认同)
  • @EamonNerbonne:让'NaN == NaN`返回true或false以外的东西会有问题,但鉴于`(a <b)`不一定等于`!(a> = b)`,我认为没有理由那个`(a == b)`必须等于`!(a!= b)`.将"NaN == NaN"和"Nan!= NaN"都返回false将允许需要相等定义的代码使用它所需的代码. (4认同)
  • 恕我直言,"NaN"违反了三分法是有道理的,但是像我一样,我认为没有合理的语义理由,因为当它的操作数都属于同一类型时,没有`==`定义等价关系(更进一步,我认为语言应该显式地禁止不同类型的事物之间的比较 - 即使存在隐式转换 - 如果这种比较不能实现等价关系).等价关系的概念在编程和数学中都是如此基础,违反它似乎是疯狂的. (3认同)
  • 我不知道有任何公理或假设表明数学对象(您甚至如何定义数学对象????)必须等于自身。 (3认同)
  • 即使你基于f(x)= x的集合S上的同一性函数f,我也会认为NaN不是数字集的一部分,毕竟,它实际上不是数字.所以我没有看到身份函数的任何论证NaN应该等于自己. (3认同)
  • @Transcendence 查找“身份法则”。平等的概念(以及它的兄弟一致性和完整性)太复杂了,无法在评论中处理;但我会说,如果你假设 x=x 可能是假的;由于 f(x)=x 中的“=”没有明确定义,因此在我们甚至可以考虑识别函数之前,您已经打开了一大堆蠕虫。 (2认同)

asf*_*107 9

一个不错的属性是:if x == x返回false,然后xNaN.

(可以使用这个属性来检查,如果xNaN或不是.)

  • 一个人可能拥有该属性,但仍然具有(Nan!= Nan)*还*返回false。如果IEEE做到了这一点,想要测试“ a”和“ b”之间的等效关系的代码可以使用“!(a!= b)”。 (2认同)

Mik*_*e C 7

试试这个:

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
Run Code Online (Sandbox Code Playgroud)

如果intA == intB为真,那可能会导致你得出a == b,但显然不是.

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

  • "这可能会让你得出结论a == b" - 但这只是一个无效的结论 - 例如strtol("010")== strtol("8"). (4认同)
  • 我不遵循你的逻辑。给定“a=16777216f”、“b=0.25”和“c=0.125”,“a+b == a+c”这一事实是否应该意味着“b==c”?或者仅仅是这两个计算产生“无法区分”的结果?为什么 sqrt(-1) 和 (0.0/0.0) 不应该被认为是无法区分的,因为缺乏区分它们的方法? (2认同)