【问题标题】:What is the rationale for all comparisons returning false for IEEE754 NaN values?对于 IEEE754 NaN 值返回 false 的所有比较的基本原理是什么?
【发布时间】:2010-12-06 14:45:44
【问题描述】:

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

我想这在某种程度上简化了数值计算,但我找不到明确说明的原因,甚至在 Kahan 的 Lecture Notes on the Status of IEEE 754 中也没有,它详细讨论了其他设计决策。

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

编辑: 到目前为止的答案都认为比较 NaN 是没有意义的。

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

因此,在我看来,比较返回 true 或 false 的选择是任意的, 对于一般数据处理,如果它遵守通常的法律将是有利的 (== 的自反性, 的三分法), 以免依赖这些定律的数据结构变得混乱。

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

编辑 2: 我想我现在明白为什么将 NaN 设为最大值是个坏主意,它会弄乱上限的计算。

NaN != NaN 可能是可取的,以避免检测循环中的收敛,例如

while (x != oldX) {
    oldX = x;
    x = better_approximation(x);
}

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

【问题讨论】:

  • 一旦 NaN 进入计算,它通常永远不会离开,因此您的收敛测试将成为无限循环。通常最好将收敛失败报告给调用例程,可能通过返回 NaN。因此,循环结构通常会变成类似while (fabs(x - oldX) > threshold) 的东西,如果收敛发生或 NaN 进入计算,则退出循环。然后将在循环外检测 NaN 和适当的补救措施。
  • 如果 NaN 是 while 循环仍然有效的顺序的最小元素。

标签: floating-point comparison nan ieee-754 iec10967


【解决方案1】:

我是 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 = infinity,因此它本身并不是一个很好的理由;它可以合理地弯曲到 (x - y == 0) or (x and y are both NaN))。

  • 更重要的是,在8087算术中将NaN形式化的时候没有isnan( )谓词;有必要为程序员提供一种方便有效的检测 NaN 值的方法,这种方法不依赖于提供诸如 isnan( ) 之类的编程语言,这可能需要很多年。我将引用 Kahan 自己关于该主题的文章:

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

请注意,这也是排除返回“非布尔值”之类的逻辑的逻辑。也许这种实用主义是错误的,标准应该要求isnan( ),但这会使NaN 在几年内几乎不可能有效和方便地使用,而世界都在等待编程语言的采用。我不相信这是一个合理的权衡。

坦率地说:NaN == NaN 的结果现在不会改变。与其在互联网上抱怨,不如学会忍受它。如果您想争论适合容器的顺序关系也应该存在,我建议您主张您最喜欢的编程语言实现 IEEE-754 (2008) 中标准化的 totalOrder 谓词。事实上,它还没有说明 Kahan 引起当前事态的担忧的有效性。

【讨论】:

  • 我阅读了您的第 1 点和第 2 点。然后我观察到在实际算术中(扩展为首先允许 NaN)NaN 等于它自己 - 仅仅是因为在数学中,任何实体都等于它自己, 毫无例外。现在我很困惑:为什么 IEEE 没有“匹配真实算术的行为”,这会使 NaN == NaN?我错过了什么?
  • 同意; NaN 的非自反性给 Python 等语言带来了无穷无尽的痛苦,因为它具有基于等式​​的包含语义。当您尝试在其之上构建容器时,您真的不希望平等成为等价关系。对于本应易于学习的语言而言,拥有两个独立的平等概念也不是一个友好的选择。结果(在 Python 的情况下)是在尊重 IEEE 754 和不太破坏的包含语义之间的令人不快的脆弱折衷。幸运的是,很少将 NaN 放入容器中。
  • 这里有一些不错的观察:bertrandmeyer.com/2010/02/06/…
  • @StephenCanon: (0/0) == (+INF) + (-INF) 比1f/3f == 10000001f/30000002f 更荒谬吗?如果浮点值被认为是等价类,那么a=b 并不意味着“产生ab 的计算,如果以无限精度完成,将产生相同的结果”,而是“什么是关于a 的已知信息与关于b 的已知信息相匹配。我很好奇您是否知道任何代码示例,其中“Nan!= NaN”使事情变得比其他情况更简单?
  • 理论上,如果您有 NaN == NaN 而没有 isNaN,您仍然可以使用 !(x < 0 || x == 0 || x > 0) 测试 NaN,但它会比 x != x 更慢且更笨拙。
【解决方案2】:

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

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

在将 sqrt(-3) 与 sqrt(-2) 进行比较的情况下,此行为也是有利的。它们都将返回 NaN,但即使它们返回相同的值,它们也不等价。因此,在处理 NaN 时,相等总是返回 false 是理想的行为。

【讨论】:

  • sqrt(1.00000000000000022)==sqrt(1.0)的结果应该是什么? (1E308+1E308-1E308-1E308-1E308)==(1E308+1E308)怎么样?此外,六个比较中只有五个返回 false。 != 运算符返回 true。让NaN==NaNNaN!=NaN 都返回false 将允许比较x 和y 的代码通过选择==!= 来选择当两个操作数都是NaN 时应该发生的情况。
【解决方案3】:

再做一个类比。如果我递给你两个盒子,告诉你它们都没有一个苹果,你会告诉我这两个盒子里的东西是一样的吗?

NaN 不包含关于某物是什么、不是什么的信息。因此,这些元素永远不能绝对说是相等的。

【讨论】:

  • 根据定义,所有空集都是相等的。
  • 给你的盒子不知道是空的。
  • 你能告诉我这些盒子里没有同样的东西吗?我可以理解(NaN==Nan)==false 的基本原理。我不明白(Nan!=Nan)==true 的基本原理。
  • 我假设 NaN != NaN 为真,因为 x != y 被定义为 !(x == y)。当然,我不知道 IEEE 规范是否这样定义。
  • 但是在这个比喻中,如果你给我一个盒子,说里面没有苹果,然后问我它是否等于它自己,你希望我说不?因为根据 IEEE,这就是我要说的。
【解决方案4】:

来自NaN 上的维基百科文章,以下做法可能会导致 NaN:

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

由于无法知道这些操作中的哪一个创建了 NaN,因此无法进行有意义的比较。

【讨论】:

  • 而且,即使你知道是哪个操作,也无济于事。我可以构造任意数量的公式,它们在某个点上达到 0/0,它们在那个点具有(如果我们假设连续性)定义明确的不同值。
【解决方案5】:

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

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

【讨论】:

    【解决方案6】:

    我猜 NaN (Not A Number) 的确切含义是:这不是一个数字,因此比较它没有任何意义。

    这有点像带有null 操作数的SQL 中的算术运算:它们都产生null

    浮点数的比较比较数值。因此,它们不能用于非数值。因此,NaN 无法在数字意义上进行比较。

    【讨论】:

    • "这不是一个数字,因此比较它没有任何意义。"字符串不是数字,但比较它们是有意义的。
    • 是的,将字符串与字符串进行比较是有意义的。但是将字符串与苹果进行比较并没有多大意义。既然苹果和梨不是数字,那么比较它们有意义吗?哪个更大?
    • @DarenThomas:在 SQL 中,“IF NULL=NULL THEN FOO;”都不是也不是“IF NullNull THEN CALL FOO;” [或任何语法] 将执行FOO。让 NaN 等价于 if (NaN != NaN) foo(); 不应该执行 foo,但它确实可以。
    【解决方案7】:

    它看起来很奇怪,因为大多数允许 NaN 的编程环境也不允许 3 值逻辑。如果您将 3 值逻辑混入其中,它就会变得一致:

    • (2.7 == 2.7) = 真
    • (2.7 == 2.6) = 假
    • (2.7 == NaN) = 未知
    • (NaN == NaN) = 未知

    即使 .NET 也不提供 bool? operator==(double v1, double v2) 运算符,所以您仍然会被愚蠢的 (NaN == NaN) = false 结果卡住。

    【讨论】:

      【解决方案8】:

      过于简单的答案是 NaN 没有数值,因此没有任何东西可以与其他任何东西进行比较。

      如果您希望 NaN 表现得像 +INF,您可以考虑测试并用 +INF 替换它们。

      【讨论】:

        【解决方案9】:

        虽然我同意 NaN 与任何实数的比较应该是无序的,但我认为将 NaN 与其自身进行比较是有正当理由的。例如,如何发现信号 NaN 和安静 NaN 之间的区别?如果我们将信号视为一组布尔值(即位向量),人们可能会询问位向量是相同还是不同,并相应地对集合进行排序。例如,在解码最大偏置指数时,如果有效数左移以便将有效数的最高有效位与二进制格式的最高有效位对齐,则负值将是安静的 NaN,任何正值将是一个信号 NaN。零当然是为无穷大而保留的,并且比较将是无序的。 MSB 对齐将允许直接比较来自不同二进制格式的信号。因此,具有相同信号集的两个 NaN 将是等价的,并赋予相等意义。

        【讨论】:

          【解决方案10】:

          因为数学是数字“存在”的领域。 在计算过程中,您必须初始化这些数字并根据您的需要保持它们的状态。 在过去,内存初始化以您永远无法依赖的方式工作。你永远不能让自己去想这个“哦,那会一直用 0xCD 初始化,我的算法不会出错”

          所以你需要适当的非混合溶剂,它足够粘,不会让你的算法被吸入和破坏。 涉及数字的好的算法大多会处理关系,那些 if() 关系将被省略。

          这只是油脂,您可以在创建时将其放入新变量中,而不是从计算机内存中编写随机地狱。而且无论你的算法是什么,都不会出错。

          接下来,当您仍然突然发现您的算法正在生成 NaN 时,可以将其清除,一次查看每个分支。同样,“总是错误”的规则在这方面有很大帮助。

          【讨论】:

            【解决方案11】:

            非常简短的回答:

            原因如下: nan / nan = 1 不得持有。否则 inf/inf 将是 1。

            (因此nan不能等于nan。至于><,如果nan会尊重满足阿基米德性质的集合中的任何顺序关系,我们将再次拥有@987654328 @在极限)。

            【讨论】:

            • 不,这没有意义。我们有inf = infinf / inf = nan,所以nan = nan 也不会阻止nan / nan = nan
            • @starblue 你是说nan / nan = 1?无论如何...如果 inf 和 nan 与任何其他数字一样,您的推理确实是有道理的。事实并非如此。 inf/inf 必须是 nan(或数学中的不确定形式)而不是 1 的原因比简单的代数操作更微妙(参见 De L'Hospital 定理)。
            猜你喜欢
            • 2017-06-16
            • 1970-01-01
            • 2014-05-01
            • 2020-02-11
            • 1970-01-01
            • 2017-09-21
            • 2018-05-16
            • 2019-03-15
            • 1970-01-01
            相关资源
            最近更新 更多