为什么浮点无穷大与 NaN 不同,是相等的?

Why are floating point infinities, unlike NaNs, equal?

为什么无穷大比较不遵循应用于 NaN 的逻辑?此代码打印出 false 三次:

double a = Double.NaN;
double b = Double.NaN;
System.out.println(a == b); // false
System.out.println(a < b); //  false
System.out.println(a > b); //  false

但是,如果我将 Double.NaN 更改为 Double.POSITIVE_INFINITY,我得到 true 表示相等,但 false 表示大于和小于比较:

double a = Double.POSITIVE_INFINITY;
double b = Double.POSITIVE_INFINITY;
System.out.println(a == b); // true
System.out.println(a < b); //  false
System.out.println(a > b); //  false

这看起来很危险。假设无限值是由溢出引起的,我想更有可能的是两个最终为无穷大的变量在完美算术中实际上并不相等。

这是因为 NaN 不是数字,因此不等于包括 NaN 在内的任何数字。

你的推理是 Double.POSITIVE_INFINITY 不应该等于它自己,因为它“很可能”是由于精度损失而获得的。

这条推理适用于所有的浮点数。任何有限值都可以作为不准确运算的结果获得。这并没有推动 IEEE 754 标准化委员会将 == 定义为总是对有限值求假,那么为什么无穷大应该不同呢?

按照定义,==对了解它做什么的人有用(即测试已经得到的浮点值,当然不是应通过实际计算获得的值)。对于理解这一点的任何人,并且即使对于不涉及无穷大的计算,您也需要理解它才能使用浮点数,将 Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY 计算为 true 是很方便的,即使只是为了测试 a 的浮点结果浮点计算是 Double.POSITIVE_INFINITY.

这就留下了为什么 NaN 可以承受特殊行为的问题,而无穷大应该遵循与有限值相同的一般原则。 NaN 不同于无穷大:IEEE 754 标准的基本原则是值就是它们本来的样子,但是运算的结果可以近似于实际结果,在这种情况下,得到的浮点值根据舍入方式得到。

暂时忘记 1.0 / 0.0 定义为 +inf,这在本次讨论中很烦人。暂时认为 Double.POSITIVE_INFINITY 只是 1.0e100 / 1.0e-300Double.MAX_VALUE + Double.MAX_VALUE 等操作的结果。对于这些操作,+inf 是最接近真实结果的近似值,就像产生有限结果的操作一样。相比之下,NaN 是您在操作没有意义时获得的结果。 NaN 的特殊行为是合理的,但 inf 只是所有太大而无法表示的值的近似值。

实际上,1.0 / 0.0也会产生+inf,但是那个应该算是一个例外。将该操作的结果定义为 NaN 一样一致,但将其定义为 +inf 在某些算法的实现中更方便。 Kahan's notes. More details than most will wish for are in the article “Branch Cuts for Complex Elementary Functions, or Much Ado About Nothing's Sign Bit” 中的第 10 页提供了一个示例。我还将 IEEE 754 中存在的与 NaN 标志分开的“被零除”标志解释为承认用户可能希望特别处理被零除,尽管它没有被定义为产生 NaN。

因为这是标准。 Infinity 表示大于或小于 Double.MAX_VALUE/-Double.MAX_VALUE 的数。

NaN 表示没有意义的操作的结果。也就是操作出来的可能不是数字。

我猜逻辑是一旦数字变得足够大(无穷大)并且由于浮点数的限制,向其添加数字不会改变结果,因此它的 'like' 无穷大。

因此,如果您想与非常大的数字进行比较,在某些时候您可能只是说这两个大数字对于所有意图和目的来说都足够接近了。但是如果你想比较两个都不是数字的东西,你不能比较它们所以它是错误的。至少你不能把它们当作原始人来比较。

另一个证明 "infinite" 值相等的观​​点是完全避免 cardinality 概念。本质上,如果您不能推测 "just how infinite a value is compared to another, given that both are infinite",那么假设 Inf = Inf 会更简单。

编辑: 为了澄清我对基数的评论,我将举两个关于无限量比较(或相等)的例子。

考虑正整数集 S1 = {1,2,3, ...} ,它是无限的。还要考虑偶数集 S2 = {2,4,6, ...},它们也是无限的。虽然 S1 中的元素数量明显是 S2 中的两倍,但它们有 "equally many" 个元素,因为您可以轻松地在集合之间建立一对一的函数,即 1 -> 22-> 4 , ... 因此它们具有相同的基数。

考虑实数集 R 和整数集 I。同样,两者都是无限集。然而,对于每个整数 i,在 (i, i+1) 之间有无穷多个实数。因此没有一对一的函数可以映射这两个集合的元素,因此它们的基数不同。

底线: 无限数量的等式很复杂,在命令式语言中更容易避免它 :)

为什么无穷大是相等的?因为它有效。

浮点运算旨在产生(相对)快速的计算并保留错误。这个想法是您在冗长的计算过程中不检查溢出或其他废话;你等到它完成。这就是 NaN 传播它们的方式的原因:一旦你得到一个 NaN,你几乎没有办法让它消失。计算完成后,您可以查找 NaN 以检查是否出现问题。

无穷大也一样:如果有溢出的可能性,不要做会丢弃无穷大的事情。

如果您想要缓慢而安全,IEEE-754 具有安装陷阱处理程序的机制,以便在计算结果为 NaN 或无穷大时向您的代码提供回调。大多数情况下没有使用;它通常太慢了,一旦代码被正确调试就毫无意义(这并不容易:人们获得了如何做好这些事情的博士学位)。

正确的答案很简单"because the standard (and the docs) say so"。但我不会愤世嫉俗,因为很明显这不是你想要的。


除了此处的其他答案,我将尝试将无穷大与饱和算术联系起来。

其他答案已经说明了 NaN 比较结果为 true 的原因,所以我不会打死马。

假设我有一个表示灰度颜色的饱和整数。为什么我要使用饱和算法?因为比白色的东西仍然是白色,比黑色的东西仍然是黑色(orange除外)。这意味着 BLACK - x == BLACKWHITE + x == WHITE。有道理吗?

现在,假设我们要用(有符号的)1s complement 8-bit integer where BLACK == -127 and WHITE == 127. Why 1s complement? Because it gives us a signed zero like IEEE 754 floating point 表示那些灰度颜色。而且,因为我们使用的是饱和算法,-127 - x == -127127 + x == 127.

这与浮点无穷大有什么关系?将整数替换为浮点数,将 BLACK 替换为 NEGATIVE_INFINITY,将 WHITE 替换为 POSITIVE_INFINITY,你会得到什么? NEGATIVE_INFINITY - x == NEGATIVE_INFINITYPOSITIVE_INFINITY + x == POSITIVE_INFINITY.

既然你用了POSITIVE_INFINITY,我也用。首先,我们需要一个 class 来表示我们基于整数的饱和颜色;我们称它为 SaturatedColor 并假设它像 Java 中的任何其他整数一样工作。现在,让我们使用您的代码,将 double 替换为我们自己的 SaturatedColor 并将 Double.POSITIVE_INFINITY 替换为 SaturatedColor.WHITE:

SaturatedColor a = SaturatedColor.WHITE;
SaturatedColor b = SaturatedColor.WHITE;

正如我们在上面建立的那样,SaturatedColor.WHITE(就是上面的 WHITE)是 127,所以让我们在这里这样做:

SaturatedColor a = 127;
SaturatedColor b = 127;

现在我们采用您使用的 System.out.println 语句并将 ab 替换为它们的值(值?):

System.out.println(127 == 127);
System.out.println(127 < 127);
System.out.println(127 > 127);

这将打印什么应该是显而易见的。

自从提到 Double.Nan.equals (Double.NaN) 以来:执行算术和比较数字时应该发生的事情是一回事,而考虑对象的行为方式时则完全是另一回事。

两个典型的问题案例是:对数字数组进行排序,使用哈希值实现字典、集合等。有两种特殊情况,其中 <、= 和 > 的正常排序不适用:一种情况是 +0 = -0,另一种情况是 NaN ≠ NaN,并且 x < NaN、x > NaN、x = NaN无论 x 是什么,它总是假的。

排序算法可能会遇到麻烦。排序算法可能假设 x = x 始终为真。因此,如果我知道 x 存储在一个数组中并寻找它,我可能不会进行任何边界检查,因为对它的搜索必须找到一些东西。如果 x 是 NaN,则不是。排序算法可能假设恰好 a < b 和 a >= b 之一必须为真。如果一个是 NaN,则不会。因此,当出现 NaN 时,朴素的排序算法可能会崩溃。在对数组进行排序时,您必须决定希望 NaN 结束的位置,然后更改比较代码以使其正常工作。

现在是字典和集合,通常是哈希:如果我使用 NaN 作为键会怎样?一组包含独特的对象。如果该集合包含一个 NaN 并且我尝试添加另一个,它是否唯一因为它不等于已经存在的那个?那么 +0 和 -0 应该被视为相等还是不同?规则是任何两个被认为相等的项目必须具有相同的散列值。因此,明智的做法是(可能)哈希函数 returns 为所有 NaN 提供一个唯一值,为 +0 和 -0 提供一个唯一值。在哈希查找之后,当您需要找到一个具有相同哈希值但实际相等的元素时,两个 NaN 应该被视为相等(但不同于其他任何元素)。

这可能就是 Double.Nan.equal () 与 == 行为不同的原因。

对我来说,"because it should behave the same as zero" 似乎是一个很好的答案。算术溢出和下溢应该可以类似地处理。

如果从可以存储在浮点数中的最大的近乎无限小的值下溢,您将得到零,并且零比较相同。

如果您从可以存储在浮点数中的最大的近乎无限大的值溢出,您将得到 INF,并且 INF 比较相同。

这意味着处理在两个方向上超出范围的数字的代码将不需要为一个或另一个单独的特殊外壳。相反,两者都需要区别对待。

"neither" 案例涵盖了最简单的要求:您想要检查某些东西是否 over/underflowed,您可以使用普通算术比较运算符将它与 zero/INF 进行比较,无需了解当前语言的检查命令的特殊语法:是 Math.isInfinite(), Float.checkForPositiveInfinity(), hasOverflowed()...?