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

我认为这在某种程度上简化了数值计算,但我找不到一个明确的原因,甚至在Kahan的《IEEE 754状态讲义》中也找不到,他详细讨论了其他设计决策。

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

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

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

所以在我看来,选择返回真或假进行比较是随意的, 对于一般的数据处理来说,如果它符合通常的规律,那将是有利的 (==的反身性,<,==,>的三分), 以免依赖这些定律的数据结构变得混乱。

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

编辑2: 我想我现在明白为什么让NaN最大是一个坏主意了,它会搞砸上限的计算。

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

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

但是最好是通过比较小极限下的绝对差来写。 所以恕我直言,这是一个相对较弱的论点,打破自反性在NaN。


当前回答

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

首先,浮点数不是实数,浮点算术不满足实数算术的公理。三分并不是真正算术中对浮点数不成立的唯一性质,甚至也不是最重要的性质。例如:

加法不是结合律。 分配律不成立。 有不带倒数的浮点数。

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

当我们可以的时候,我们匹配真实算术的行为。 当我们做不到的时候,我们会尽量让违规行为变得可预测,并且尽可能容易诊断。

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

我提到过三分法不适用于浮点值。然而,有一个类似的性质是成立的。754-2008标准第2段第5.11条:

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

就编写额外的代码来处理nan而言,通常有可能(尽管并不总是容易)以正确的方式构建代码以使nan失败,但情况并非总是如此。如果不是,则可能需要一些额外的代码,但这只是代数闭包为浮点算术带来的便利所付出的小小代价。


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

通过与Kahan的交谈,我的理解是NaN != NaN起源于两个务实的考虑:

That x == y should be equivalent to x - y == 0 whenever possible (beyond being a theorem of real arithmetic, this makes hardware implementation of comparison more space-efficient, which was of utmost importance at the time the standard was developed — note, however, that this is violated for x = y = infinity, so it’s not a great reason on its own; it could have reasonably been bent to (x - y == 0) or (x and y are both NaN)). More importantly, there was no isnan( ) predicate at the time that NaN was formalized in the 8087 arithmetic; it was necessary to provide programmers with a convenient and efficient means of detecting NaN values that didn’t depend on programming languages providing something like isnan( ) which could take many years. I’ll quote Kahan’s own writing on the subject:

Were there no way to get rid of NaNs, they would be as useless as Indefinites on CRAYs; as soon as one were encountered, computation would be best stopped rather than continued for an indefinite time to an Indefinite conclusion. That is why some operations upon NaNs must deliver non-NaN results. Which operations? … The exceptions are C predicates “ x == x ” and “ x != x ”, which are respectively 1 and 0 for every infinite or finite number x but reverse if x is Not a Number ( NaN ); these provide the only simple unexceptional distinction between NaNs and numbers in languages that lack a word for NaN and a predicate IsNaN(x).

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

坦率地说:NaN == NaN的结果现在不会改变。与其在网上抱怨,不如学着接受现实。如果你想证明适合容器的顺序关系也应该存在,我建议你提倡你最喜欢的编程语言实现IEEE-754(2008)中标准化的totalOrder谓词。事实上,它还没有证明卡汉的担忧是正确的,正是这种担忧促使了目前的事态。

其他回答

因为数学是数字“仅仅存在”的领域。 在计算中,你必须初始化这些数字,并根据需要保持它们的状态。 在过去的日子里,内存初始化的工作方式是你永远无法依赖的。你永远不能允许自己这样想“哦,它会一直用0xCD初始化,我的算法不会坏”。

所以你需要合适的非混合溶剂,足够粘稠,不会让你的算法被卷入和破坏。 涉及数字的优秀算法大多使用关系,而那些if()关系将被省略。

这只是油脂,你可以在创建新变量,而不是从计算机内存编程随机地狱。不管你的算法是什么,都不会崩溃。

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

从wikipedia关于NaN的文章来看,以下做法可能导致NaN:

All mathematical operations> with a NaN as at least one operand The divisions 0/0, ∞/∞, ∞/-∞, -∞/∞, and -∞/-∞ The multiplications 0×∞ and 0×-∞ The additions ∞ + (-∞), (-∞) + ∞ and equivalent subtractions. Applying a function to arguments outside its domain, including taking the square root of a negative number, taking the logarithm of a negative number, taking the tangent of an odd multiple of 90 degrees (or π/2 radians), or taking the inverse sine or cosine of a number which is less than -1 or greater than +1.

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

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

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

这种行为在比较根号(-3)和根号(-2)的情况下也很有利。它们都会返回NaN,但它们并不等效,即使它们返回相同的值。因此,在处理NaN时,具有相等总是返回false是理想的行为。

再打个比方。如果我给你两个盒子,告诉你两个盒子里都没有苹果,你会告诉我两个盒子里装的是同样的东西吗?

NaN不包含某物是什么,只包含它不是什么。因此,这些元素不能肯定地说相等。

我猜NaN(不是一个数字)的意思就是:这不是一个数字,因此比较它是没有意义的。

这有点像SQL中带有空操作数的算术:它们的结果都为空。

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