对于IEEE754 NaN值,所有比较返回false的理由是什么?

sta*_*lue 240 floating-point comparison nan ieee-754 iec10967

为什么NaN值的比较与所有其他值的行为不同?也就是说,与运算符==,<=,> =,<,>的所有比较(其中一个或两个值为NaN)返回false,这与所有其他值的行为相反.

我想这可以通过某种方式简化数值计算,但我找不到明确说明的理由,甚至在Kahan 的IEEE 754状态讲义中也没有详细讨论其他设计决策.

这种异常行为在进行简单数据处理时会造成麻烦.例如,当在C程序中对某些实值字段的记录列表进行排序时,我需要编写额外的代码来处理NaN作为最大元素,否则排序算法可能会变得混乱.

编辑: 迄今为止的答案都认为比较NaNs毫无意义.

我同意,但这并不意味着正确的答案是错误的,而是一个非布尔值(NaB),幸运的是它不存在.

因此,在我看来,选择返回真或假的比较是任意的,对于一般数据处理,如果它遵循通常的定律(==的反射性,<= =,>的三分法),那将是有利的,以免数据结构依赖这些法律变得困惑.

因此,我要求打破这些法律的一些具体优势,而不仅仅是哲学推理.

编辑2: 我想我现在理解为什么使NaN最大化是一个坏主意,它会搞砸上限的计算.

可能需要NaN!= NaN以避免检测环路中的收敛,例如

while (x != oldX) {
    oldX = x;
    x = better_approximation(x);
}
Run Code Online (Sandbox Code Playgroud)

然而,最好通过比较绝对差异与小限制来编写.所以恕我直言,这是打破NaN反身性的一个相对弱的论据.

Ste*_*non 481

我是IEEE-754委员会的成员,我会尽力帮助澄清一些事情.

首先,浮点数不是实数,浮点运算不满足实数算术的公理.三分法不是真正算术的唯一属性,它不适用于浮点数,甚至也不是最重要的.例如:

  • 加法不是关联的.
  • 分配法并不成立.
  • 有没有反转的浮点数.

我可以继续 不可能指定一个固定大小的算术类型来满足我们所熟知和喜爱的所有实际算术属性.754委员会必须决定弯曲或打破其中一些.这是由一些非常简单的原则指导:

  1. 当我们可以时,我们匹配真实算术的行为.
  2. 当我们做不到时,我们会尽量使违规行为变得可预测并且易于诊断.

关于你的评论"这并不意味着正确答案是错误的",这是错误的.谓词(y < x)询问是否y小于x.如果y是NaN,则它小于任何浮点值x,因此答案必然是错误的.

我提到三分法不适用于浮点值.但是,有一个类似的属性确实成立.754-2008标准第5.11条第2款:

四种相互排斥的关系是可能的:小于,等于,大于和无序.当至少一个操作数是NaN时,出现最后一种情况.每个NaN都应该将无序与包括其自身在内的所有东西进行比较.

至于编写额外的代码来处理NaN,通常可能(虽然并不总是很容易)以一种NaN正确落入的方式构造代码,但情况并非总是如此.如果不是这样,可能需要一些额外的代码,但这对于代数闭包带来浮点运算的便利性来说是一个很小的代价.


附录:许多评论者认为,保持平等和三分法的反身性更为有用,理由是采用NaN!= NaN似乎并没有保留任何熟悉的公理.我承认对这个观点有一些同情,所以我想我会重新审视这个答案,并提供更多的背景.

我与Kahan谈话的理解是NaN!= NaN起源于两个实用的考虑因素:

  • x == y应该等同于x - y == 0任何可能的(除了作为真实算术的定理之外,这使得比较的硬件实现更节省空间,这在标准开发时是最重要的 - 但请注意,这违反了x = y =无穷大,所以它本身并不是一个很好的理由;它可以合理地弯曲到(x - y == 0) or (x and y are both NaN)).

  • 更重要的是,当时没有isnan( )断言NaN在8087算术中被形式化; 有必要为程序员提供一种方便有效的检测NaN值的方法,这些方法不依赖于编程语言,提供isnan( )可能需要很多年的东西.我将引用卡汉自己关于这个主题的文章:

如果没有办法摆脱NaNs,它们就像CRAYs上的Indefinites一样无用; 一旦遇到一个人,计算将最好停止,而不是无限期地持续到无限期结束.这就是为什么NaN上的某些操作必须提供非NaN结果的原因.哪个操作?......例外是C谓词"x == x"和"x!= x",对于每个无限或有限数x,它们分别为1和0,但如果x不是数字(NaN)则反向; 这些提供了NaNs与缺少NaN单词和谓词IsNaN(x)的语言中数字之间唯一的简单区分.

请注意,这也是排除返回"Not-A-Boolean"之类的逻辑.也许这种实用主义是错误的,标准应该是必需的isnan( ),但是这将使NaN几乎不可能在世界等待编程语言采用的情况下有效和方便地使用几年.我不相信那是一次合理的权衡.

直言不讳:NaN == NaN的结果现在不会改变.更好地学会忍受它而不是在互联网上抱怨.如果你想争论一个适合容器的订单关系应该存在,我建议你提倡你最喜欢的编程语言实现totalOrderIEEE-754(2008)标准化的谓词.事实上,它还没有谈到Kahan关注的有效性,这种关注促成了当前的事态.

  • 我读了你的第1点和第2点.然后我观察到在实数算术中(扩展到首先允许NaN)NaN等于它自己 - 只是因为在数学中,任何实体都等于它自己,毫无例外.现在我很困惑:为什么IEEE没有"匹配真实算术的行为",这会使NaN == NaN?我错过了什么? (15认同)
  • 同意; NaNs的非自反性为Python这样的语言创造了痛苦的终结,其基于等同的包含语义.当你试图在它上面构建容器时,你真的*不希望等式不能成为等价关系.对于一种应该易于学习的语言而言,拥有两个单独的平等概念也不是一个友好的选择.结果(在Python的情况下)是尊重IEEE 754和不太破坏的包含语义之间令人不快的脆弱折衷.幸运的是,将NaN放入容器中很少见. (10认同)
  • 如果你有 NaN == NaN 并且没有 IsNaN,你也可以简单地用 `x == NaN` 来测试 NaN。 (10认同)
  • 这里有一些很好的观察:http://bertrandmeyer.com/2010/02/06/reflexivity-and-other-pillars-of-civilization/ (5认同)
  • @StephenCanon:以(1/0/3f == 10000001f/30000002f`为例,(0/0)==(+ INF)+( - INF)会以什么方式更无意义?如果浮点值被认为是等价类,则"a = b"并不意味着"产生"a"和"b"的计算,如果以无限精度完成,将产生相同的结果",而是"关于"a"的所谓已知与"b`"相关的内容.我很好奇,如果你知道任何代码的例子,其中"Nan!= NaN"使事情变得比其他情况更简单? (5认同)
  • @StephenCanon 通过检查 `x == (0/0)` 不是可以轻松绕过吗?即使您没有“NaN”这个词,生成一个也应该很容易。 (4认同)
  • 从理论上讲,如果您有NaN == NaN而没有isNaN,您仍然可以使用`!(x &lt;0 || x == 0 || x&gt; 0)`来测试NaN,但是它会比`慢且笨拙。 x!= x`。 (3认同)
  • @JSQuareD:IEEE 754 的起源背景与 8087 算术在这里很重要;在它被引入时,还有其他浮点算术在使用,其中一些会陷入 0/0。`x != x` 可以用作对 NaN 的检查,这对于其他算术也是安全的(它可能总是返回 true,但不会陷入陷阱)。 (3认同)
  • @StephenCanon:确实浮点数学不能表现为一个组,更不用说环或字段,但这并不意味着浮点数不应该定义等价关系.据我所知,IEEE在2007年推荐了一套传递关系运算符,但我发现自己很困惑为什么在第一次有人试图对包含NaN的数字列表进行排序时应该显而易见的问题仍然需要一个棘手的多步骤解决方法.排序代码通常是对浮点比较更具性能敏感性的用法之一...... (2认同)
  • @MarkDickinson *幸运的是,很少将 NaN 放入容器中。*,除非它是浮点数组,例如在 NumPy 或 Matlab 中,后者有一个特殊的函数来比较数组是否[等于 nans](http: //uk.mathworks.com/help/matlab/ref/isequaln.html),[NumPy 也存在类似的需求](http://stackoverflow.com/q/10710328/974555)。 (2认同)
  • 我认为,除了“没有足够的经验来避免当时的错误”之外,不应将过去的决策合理化。一个更令人震惊的例子是浮点异常,几乎没有人会认为这不是一个非常昂贵的错误。或以太网电缆中两个中间对的布置方式;多年来,人们不得不为此付出数十亿美元的不必要的劳动力成本,更不用说轻微的信号衰减了。现在我们知道的更好了,但是那时候还不为人所知,而且如果没有事后看来,这似乎是一个非常合理的决定。 (2认同)
  • @Dmytry 是的,我确定。该答案来自 2012 年,特定于 armv7 ios 设备。所有 64b ios 设备默认在启用非规范化的情况下运行,并且在几年前没有与非规范化相关的减速。(第一代 64b ios 设备有很小的性能损失)。 (2认同)
  • @DonHatch我既无意也无意屈服,但您当然可以进行编辑。 (2认同)

小智 49

NaN可以被认为是未定义的状态/数字.类似于0/0未定义或sqrt(-3)的概念(在浮点所在的实数系统中).

NaN用作此未定义状态的一种占位符.从数学上讲,undefined不等于undefined.你也不能说未定义的值大于或小于另一个未定义的值.因此,所有比较都返回错误.

在将sqrt(-3)与sqrt(-2)进行比较的情况下,此行为也很有用.他们都会返回NaN,但即使他们返回相同的值,他们也不是等价的.因此,在处理NaN时,等式总是返回false是期望的行为.

  • +1引入`sqrt(a)= sqrt(b)<=> a = b` (8认同)
  • sqrt(1.00000000000000022)== sqrt(1.0)的结果应该是什么?(1E308 + 1E308-1E308-1E308-1E308)==(1E308 + 1E308)怎么样?此外,六个比较中只有五个返回错误.`!=`运算符返回true.将'NaN == NaN`和'NaN!= NaN`都返回false将允许比较x和y的代码选择当两个操作数都是NaN时通过选择`==`或`!=`来发生的事情. (5认同)

Jac*_*yan 34

再说一个类比.如果我递给你两个盒子,告诉你它们都没有苹果,你会告诉我盒子里面有同样的东西吗?

NaN不包含有关什么是什么的信息,而不包含什么.因此,这些元素绝对不能说是平等的.

  • 你被给出的盒子不是空的. (28认同)
  • 根据定义,所有空集都是相等的. (6认同)
  • 你能告诉我盒子里面没有同样的东西吗?我能理解`(NaN == Nan)== false`的基本原理.我不明白的是`(Nan!= Nan)== true`的基本原理. (6认同)
  • 但是在这个比喻中,如果你给了我一个盒子,说它不含苹果,那么问我是否等于自己,你希望我说不?因为根据IEEE,我不得不说. (5认同)
  • 我假设NaN!= NaN为真,因为x!= y定义为!(x == y).当然,我不知道IEEE规范是否以这种方式定义它. (3认同)
  • 我的意思是两个“NaN”值彼此完全无法区分。因此,说它们相等似乎是相当合理的,因为实际上不可能证明其他情况。在我看来,破坏“==”对于像这样有问题的东西的反身性是非常愚蠢的。其一,它使得许多潜在的优化(例如用于简化大型对象的相等性检查的指针相等性)变得不合理,并且它只会使多态推理变得非常困难,因为如果没有很多额外的小心,就无法再利用多态相等性的自反性定律。 (3认同)
  • @分号顺便说一句,NaN确实支持有效载荷,理论上该载荷可用于编码失败原因(例如0/0与∞/∞)-/sf/ask/2377746311/做浮点南有效载荷 (2认同)

Ste*_*sek 12

从关于NaN的维基百科文章中,以下实践可能会导致NaN:

  • 所有数学运算>使用NaN作为至少一个操作数
  • 划分0/0,∞/∞,∞/-∞,-∞/∞和-∞/-∞
  • 乘法0×∞和0×-∞
  • 加法∞+( - ∞),( - ∞)+∞和等价减法.
  • 将函数应用于其域外的参数,包括取负数的平方根,取负数的对数,取90度(或π/ 2弧度)的奇数倍的正切,或取反正弦或小于-1或大于+1的数字的余弦.

由于无法知道哪些操作创建了NaN,因此没有办法比较它们是否有意义.

  • 而且,即使你知道哪个操作,它也无济于事.我可以构造任意数量的公式,这些公式在某个点上达到0/0,它们(如果我们假设连续性)在那个点上有明确定义的和不同的值. (3认同)

Ric*_*gan 5

我不知道设计原理,但这是 IEEE 754-1985 标准的摘录:

“即使操作数的格式不同,也可以比较所有支持格式的浮点数。比较是精确的,不会上溢或下溢。可能有四种互斥关系:小于、等于、大于和无序.最后一种情况出现在至少一个操作数是 NaN 时。每个 NaN 都应与所有内容进行无序比较,包括它自己。”