320

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

我想这在某种程度上简化了数值计算,但我找不到明确说明的原因,甚至在Kahan的关于 IEEE 754 状态的讲义中也没有,它详细讨论了其他设计决策。

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

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

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

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

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

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

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

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

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

4

11 回答 11

656

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

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

  • 加法不是关联的。
  • 分配法则不成立。
  • 有没有倒数的浮点数。

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

  1. 如果可以,我们会匹配真实算术的行为。
  2. 当我们做不到时,我们会尝试使违规行为尽可能可预测且易于诊断。

关于您的评论“这并不意味着正确答案是错误的”,这是错误的。谓词(y < x)询问是否y小于x。如果y是 NaN,那么它小于任何浮点值x,所以答案必然为假。

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

可能有四种互斥关系:小于、等于、大于和无序。最后一种情况出现在至少一个操作数是 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))。

  • 更重要的是,在 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 和数字之间唯一简单的无异常区别。

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

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

于 2009-10-15T17:00:03.807 回答
58

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

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

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

于 2009-10-14T09:38:00.980 回答
47

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

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

于 2009-10-14T09:41:39.890 回答
14

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

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

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

于 2009-10-14T09:41:51.857 回答
5

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

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

于 2009-10-14T13:57:38.517 回答
2

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

这有点像带有操作数的 SQL 中的算术null:它们都导致null.

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

于 2009-10-14T09:26:04.257 回答
2

它只是看起来很奇怪,因为大多数允许 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结果所困扰。

于 2009-10-14T10:59:21.943 回答
1

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

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

于 2009-10-15T17:54:27.237 回答
0

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

于 2014-05-18T19:22:17.333 回答
-4

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

因此,您需要适当的非混合溶剂,该溶剂具有足够的粘性,不会让您的算法被吸入和破坏。涉及数字的好的算法大多会处理关系,而那些if()关系将被省略。

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

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

于 2017-06-05T03:48:28.763 回答
-6

非常简短的回答:

因为以下内容: nan / nan = 1 不得持有。否则inf/inf为 1。

(因此nan不能等于nan。至于>or <,如果nan要尊重满足阿基米德性质的集合中的任何顺序关系,我们将再次nan / nan = 1处于极限)。

于 2018-06-03T15:57:10.470 回答