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

时间:2015-02-18 13:21:24

标签: java floating-point comparison nan infinity

为什么无穷大比较遵循应用于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

这似乎很危险。假设溢出产生无限值,我认为最终作为无穷大的两个变量在完美算术中实际上并不相等。

8 个答案:

答案 0 :(得分:70)

你的理由是Double.POSITIVE_INFINITY不应该与自己相等,因为它“很可能”是因为精确度下降而获得的。

这一推理线适用于所有浮点数。由于操作不准确,可以获得任何有限值。这并没有促使IEEE 754标准化委员会将==定义为总是为有限值评估为假,那么为什么无穷大会有所不同呢?

根据定义,==对于了解其功能的人(即测试已获得的浮点值,当然不是应该具有的值)非常有用已经通过实际计算获得)。对于任何理解这一点的人,你需要理解它甚至对于不涉及无穷大的计算使用浮点数,让Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY求值为true是方便的,如果只是为了测试浮点数是否为浮点计算是Double.POSITIVE_INFINITY

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

忘记1.0 / 0.0被定义为+ inf的瞬间,这在本次讨论中是一个烦恼。只考虑Double.POSITIVE_INFINITY1.0e100 / 1.0e-300等操作后的Double.MAX_VALUE + Double.MAX_VALUE时刻。对于这些操作,+ inf是实际结果的最接近的近似值,就像产生有限结果的操作一样。相比之下,NaN是您在操作没有意义时获得的结果。让NaN特别表现是可以辩护的,但inf只是所有值太大而无法表示的近似值。

实际上,1.0 / 0.0也会产生+ inf,但 应该被视为例外。将操作的结果定义为NaN同样一致,但在某些算法的实现中将其定义为+ inf更方便。 Kahan's notes中的第10页提供了一个示例。比大多数人希望的更多细节都在article “Branch Cuts for Complex Elementary Functions, or Much Ado About Nothing's Sign Bit”。我还要解释IEEE 754中存在一个与NaN标志分开的“除零”标志,因为认识到用户可能希望将除以零处理,尽管它没有被定义为生成NaN。

答案 1 :(得分:6)

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

NaN代表了一项没有意义的操作的结果。也就是说,操作可能没有数字。

我猜这个逻辑是一旦数字变得足够大(无穷大),并且由于浮点数的限制,为它添加数字不会改变结果,所以它就像&#39;&#39;喜欢&#39 ;无穷大。

因此,如果你想要与真正的大数字进行比较,那么在某些时候你可能会说这两个大数字足够接近所有意图和目的。但是如果你想比较两个都不是数字的东西,你就不能比较它们,所以它是假的。至少你无法将它们作为原语进行比较。

答案 2 :(得分:5)

为什么无限等于?因为它有效。

浮点运算旨在生成(相对)快速计算以保留错误。这个想法是你在漫长的计算过程中不检查溢出或其他废话;你等到它完成了。这就是为什么NaN以他们的方式传播的原因:一旦你获得了NaN,你可以做很少的事情就会让它消失。计算完成后,您可以查找NaN以检查是否出现了问题。

同样的无限性:如果有可能出现溢出,不要做一些会丢弃无限的事情。

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

答案 3 :(得分:4)

证明“无限”值相等的另一个观点是完全避免cardinality概念。基本上如果你不能推测“只要两个值是无限的,那么一个值与另一个值的比较有多大”,假设Inf = Inf就更简单了。

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

考虑一组正整数S1 = {1,2,3, ...},它是无限的。还要考虑偶数整数S2 = {2,4,6, ...}的集合,它们也是无限的。虽然S1中的元素明显多于S2中的两倍,但它们具有“同样多”的元素,因为您可以轻松地在集合之间具有一对一的功能,即1 -> 22-> 4, ......因此它们具有相同的基数。

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

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

答案 4 :(得分:3)

对我而言,似乎“因为它应该与零相同”会得到一个好的答案。算术上溢和下溢应该可以类似地进行处理。

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

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

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

最简单的要求被“两种情况”所涵盖:你想要检查是否存在溢出/下溢,你只需使用普通的算术比较运算符就可以将它与零/ INF进行比较,而无需了解当前语言的特殊情况检查命令的语法:是Math.isInfinite(),Float.checkForPositiveInfinity(),hasOverflowed()......?

答案 5 :(得分:2)

正确的答案很简单&#34;因为the standard(和the docs)这样说&#34;。但我不会愤世嫉俗,因为很明显,这不是你所追求的。


除了这里的其他答案之外,我还尝试将无穷大与饱和算术联系起来。

其他答案已经说过,对NaNs进行比较的原因是true,所以我不会打败死马。

我们说我有一个代表灰度颜色的饱和整数。为什么我使用饱和算术?因为更亮比白色还要白,而且更深的任何东西仍然是黑色(orange除外)。这意味着BLACK - x == BLACKWHITE + x == WHITE。有意义吗?

现在,让我们说我们希望用BLACK == -127WHITE == 127的(带符号)1s complement 8位整数表示那些灰度颜色。为什么1s补充?因为它为我们提供了signed zero 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,我也会使用它。首先,我们需要一个类来表示我们饱和的基于整数的颜色;让它调用它SaturatedColor并假设它像Java中的任何其他整数一样工作。现在,让我们接受您的代码并将double替换为我们自己的SaturatedColorDouble.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);

这将是显而易见的。

答案 6 :(得分:2)

因为提到了Double.Nan.equals(Double.NaN):当你执行算术和比较数字时应该发生的事情是一件事,当你考虑对象应该如何表现时,这是完全不同的事情。

两个典型的问题包括:对数字数组进行排序,并使用哈希值来实现字典,集等。有两种例外情况,正常的顺序是&lt;,=和&gt;不适用:一种情况是+0 = -0,另一种情况是NaN≠NaN,x <1。 NaN,x>无论x是什么,NaN,x = NaN总是假的。

排序算法可能会遇到麻烦。排序算法可以假设x = x总是为真。因此,如果我知道x存储在数组中并查找它,我可能不会进行任何边界检查,因为搜索它必须找到一些东西。如果x是NaN则不是。排序算法可以假设恰好一个&lt; b和a&gt; = b必须为真。如果一个是NaN就不行。因此,当NaN存在时,天真的排序算法可能会崩溃。在排序数组时,您必须决定NaNs最终要在哪里结束,然后更改比较代码以使其正常工作。

现在字典和集合以及一般哈希:如果我使用NaN作为密钥怎么办?集合包含唯一对象。如果集合包含NaN并且我尝试添加另一个,它是否唯一,因为它不等于已经存在的那个?如果它们被视为相同或不同,+ 0和-0怎么样?规则是任何两个被认为相等的项必须具有相同的哈希值。所以明智的事情是(可能)哈希函数为所有NaN返回一个唯一值,并为+0和-0返回一个唯一值。在哈希查找之后,当你需要找到一个实际上相同的哈希值的元素时,应该认为两个NaN相等(但与其他任何东西都不同)。

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

答案 7 :(得分:1)

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