与 NaN 不同,为啥浮点无穷大是相等的?
Posted
技术标签:
【中文标题】与 NaN 不同,为啥浮点无穷大是相等的?【英文标题】:Why are floating point infinities, unlike NaNs, equal?与 NaN 不同,为什么浮点无穷大是相等的? 【发布时间】:2015-04-19 12:23:40 【问题描述】:为什么无限比较不遵循应用于 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
这似乎很危险。假设无限值是由溢出产生的,我想两个以无穷大结尾的变量在完美算术中实际上并不相等。
【问题讨论】:
@giorashc 问题是why is Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY true
不同意它是重复的。这个问题涉及到它的 POSITIVE_INFINITY 方面,这个问题是有效的。两个无穷大不一定相等,尤其是在浮点溢出变为有符号无穷大的 Java 上下文中。可以基于此比较两个操作结果并返回 true(因为两者都是 POSITIVE INFINITY),但实际上结果会有所不同。
不管无穷大:使用==
比较浮点数或双精度总是危险的!
@piet.t 事实是,人们不应该假设浮点计算的行为与其数学同义词一样,它总是以你的方式被双曲线和错误地陈述刚刚做了。浮点相等在很多情况下都很有用,而且它并不比赋值(也不同于数学中变量的行为方式)或显式内存管理(也不同于数学中的行为方式)更危险。
为什么任何事物的行为方式与完全不同的事物不同?为什么我们会期望一个不是数字的东西像数字一样具有可比性?伟大的哲学问题,糟糕的堆栈溢出问题。
【参考方案1】:
您的推理是 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_INFINITY
视为1.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。
【讨论】:
很有趣,也很公平。目前我面前没有编译器,想知道 Double.POSITIVE_INFINITY.equals(Double.POSITIVE_INFINITY);回报?只是想知道这一点,因为当您考虑它时, Double.POSITIVE_INFINITY 将是计算返回的内容,因此 .equals() 的行为正如您所指出的那样真正有趣。 @MikeCurry 我面前也没有电脑,所以如果我接受true
并让你留下false
,对吗?
@CarlosBribiescas NaN 与无穷大不同:IEEE 754 标准的基本原则是值就是它们的本来面目,但是运算的结果可以近似于真实结果,在这个情况下,根据舍入方式得到得到的浮点值。通过这种近似,您可以获得一个 inf:例如,1.0e100 / 1.0e-300
。 NaN 是您在操作没有意义时获得的结果。让 NaN 表现特殊是有道理的,但 inf 只是所有太大而无法表示的值的近似值
快速更新,Pascal 是对的:Double.NaN.equals(Double.NaN)
在 JDK7 上返回 true
@CarlosBribiescas ...您提到了它的一种表示形式,这是一个完全正交的讨论。无论如何,NaN 是一个特例。无穷大不是:它们近似值太大而无法表示,例如将1.0e100
除以1.0e-300
时。由于所有有限值也用于表示无法精确表示的运算结果,因此没有理由将无穷大与==
定义中的有限值区别对待。【参考方案2】:
另一个证明“无限”值相等的观点是完全避免cardinality 概念。本质上,如果您无法推测“一个值与另一个值相比有多无限,鉴于两者都是无限的”,那么假设Inf = Inf
会更简单。
编辑:作为对我关于基数的评论的澄清,我将给出两个关于无限量比较(或相等)的示例。
考虑一组正整数S1 = 1,2,3, ...
,它是无限的。还要考虑偶数集合S2 = 2,4,6, ...
,它们也是无限的。虽然 S1 中的元素显然是 S2 中的两倍,但它们具有“同样多”的元素,因为您可以轻松地在集合之间实现一对一的功能,即1 -> 2
、2-> 4
,......因此具有相同的基数。
请考虑实数集R
和整数集I
。同样,两者都是无限集。然而,对于每个整数i
,(i, i+1)
之间有无限多个实数。因此没有一对一的函数可以映射这两个集合的元素,因此它们的基数是不同的。
底线: 无限量的等式很复杂,在命令式语言中更容易避免 :)
【讨论】:
“R = (-infinity, infinity)”中的“无穷大”与基数无关。 @NajibIdrissi 不确定您的观点是什么,但在理论上比较(或相等)无限量如何与基数有关。我将添加细节以使我的观点更清楚。 不,你误解了基数代表什么。它们只是一种计算集合中有多少元素的方法。这与在“当 n 趋于无穷时 x_n 的极限是无穷大”的意义上比较“无穷大”没有太大关系,这是说当 n 增长时 x_n 最终变得比任何数字都大的一种方式。也许你会对Hyperreal numbers 感兴趣,这是一种形式化的方法,例如,n^2 的增长速度比 n 快。 无限集具有未定义的基数,因此它们是无限的。莱布尼茨确定有两个无穷大,整数无穷大和浮点无穷大,即无穷数和无穷小。计算机不可能轻易地确定它正在比较哪种类型的无穷大,因此无法判断它是等于、小于还是大于。【参考方案3】:对我来说,“因为它的行为应该与零相同”似乎是一个很好的答案。算术上溢和下溢应该可以类似地处理。
如果您从可以存储在浮点数中的最大的近乎无限小的值下溢,则得到零,并且零比较相同。
如果从可以存储在浮点数中的最大的近乎无限大的值溢出,则会得到 INF,并且 INF 比较相同。
这意味着处理两个方向都超出范围的数字的代码不需要单独的特殊情况。相反,两者都需要区别对待。
“两者都没有”的情况涵盖了最简单的要求:您想检查某些内容是否上溢/下溢,您可以仅使用普通算术比较运算符将其与零/INF 进行比较,而无需了解当前语言的特殊情况检查命令的语法:是 Math.isInfinite()、Float.checkForPositiveInfinity()、hasOverflowed()...?
【讨论】:
恕我直言,下溢应该产生“正无穷小”或“负无穷小”值,它们在算术上可以比较等于“精确”零或无符号零(除了精确零之外的相同有限值之间的差异);这将允许正值和负值对称地表现,而不是在执行加法时将无法与零区分的值“假定”为正值。 @supercat 虽然我同意这是处理零的一种可能方法,但它不是已经完成的;对于要放置浮点的绝大多数用途,这将浪费芯片逻辑。此外,INF+1=INF
仍然是无穷大。但是INFIMAL+1=???
是1
吗? INFIMAL_PLUS_ONE
?从 +INF 到 -INF 的所有内容都应被视为有效数字,因此该数字空间的绝大多数是无效的,因为它与可指定的值相差无几,这将是奇怪和错误的。有了零,INF 的行为是最一致的选择。
即使 ±INFIMAL 仅存在于零附近,比较仍然比可怕的“您无法使用普通测试运算符测试的 NaN”废话更有意义。 NaN 没有可用的数字运算符是有道理的,因为根据定义它不是数字 :)
将任何内容添加到精确零将产生精确零;将无穷小添加到相反符号之一或无符号零将产生无符号零;在所有其他情况下,无穷小和无符号零将表现为加法恒等式。将 anything 乘以精确的零将产生精确的零;将无符号零或无穷小乘以无穷值将产生 NaN。将一个无穷小乘以一个有限正数将产生一个类似符号的无穷小;将 1 乘以一个有限的负数会产生一个无穷小的相反符号。
基本上,无穷小现在的行为类似于正零和负零,但是产生零的加法/减法将产生无符号零而不是无穷小。精确的零表现为加法单位元素,乘法零将允许编译器将x+0.0
替换为x
,将0.0*x
替换为0.0
,这是IEEE-754 不允许的替换。这也意味着 (-x)+(-y) 将等于 -(x+y),当 x
和 y
为正零和负零时,该等式目前失败。【参考方案4】:
既然提到了 Double.Nan.equals (Double.NaN):当您执行算术和比较数字时应该发生的事情是一回事,而当您考虑对象应该如何表现时则是完全不同的事情。
两个典型的问题案例是:对数字数组进行排序,以及使用哈希值来实现字典、集合等。有两种例外情况,其中 的正常排序不适用:一种情况是 +0 = -0,另一种情况是 NaN ≠ NaN,并且 x NaN,x = NaN无论 x 是什么,都将永远为假。
排序算法可能会遇到麻烦。排序算法可能假设 x = x 始终为真。因此,如果我知道 x 存储在一个数组中并查找它,我可能不会进行任何边界检查,因为对它的搜索必须找到一些东西。如果 x 是 NaN,则不是。排序算法可能假设 a = b 中的一个必须为真。如果一个是NaN,则不是。因此,当存在 NaN 时,简单的排序算法可能会崩溃。在对数组进行排序时,您必须决定希望 NaN 结束的位置,然后更改比较代码以使其正常工作。
现在字典和集合以及一般散列:如果我使用 NaN 作为键怎么办?一个集合包含唯一的对象。如果该集合包含一个 NaN 并且我尝试添加另一个,它是否是唯一的,因为它不等于已经存在的那个? +0 和 -0 怎么样,它们应该被视为相等还是不同?有一个规则是任何两个被认为相等的项目必须具有相同的哈希值。因此,明智的做法是(可能)散列函数为所有 NaN 返回一个唯一值,并为 +0 和 -0 返回一个唯一值。在哈希查找之后,当您需要找到具有相同哈希值且实际上相等的元素时,两个 NaN 应该被视为相等(但不同于其他任何元素)。
这可能就是 Double.Nan.equal () 的行为与 == 不同的原因。
【讨论】:
我真的希望 IEEE-754 已经指定正确的实现必须包括有效执行“数学”和“数据处理”比较的方法(语言可以为一种和两个参数定义运算符另一个功能);事实上,该标准鼓励人们编写在包含NaN
的数据上运行时会严重失败的代码,即使通过使NaN
排序高于PositiveInfinity
或低于NegativeInfinity
可以实现可接受的行为,甚至如果某些操作产生的NaN
值以一种方式和另一种方式产生。
例如,当运行中位数以查找 100 项数据集的第 20 和 80 个百分位数时,在数据集中包含 NaN
应该会产生一些不确定性,这些值应该是什么,但只有一点点。然而,除非排序代码明确处理NaN
,否则代码可能会非常高兴地决定将第 20 个百分位报告为高于第 80 个百分位。【参考方案5】:
正确的答案很简单,“因为the standard(和the docs)这么说”。但我不会愤世嫉俗,因为很明显这不是你想要的。
除了这里的其他答案,我将尝试将无穷大与饱和算术联系起来。
其他答案已经说明了对 NaN 的比较导致true
的原因,所以我不会打死马。
假设我有一个表示灰度颜色的饱和整数。为什么我使用饱和算术?因为任何比白色亮的东西仍然是白色,而任何比黑色暗的东西仍然是黑色(orange 除外)。这意味着BLACK - x == BLACK
和WHITE + x == WHITE
。有意义吗?
现在,假设我们想用(有符号的)1s complement 8 位整数表示这些灰度颜色,其中BLACK == -127
和WHITE == 127
。为什么用 1s 补码?因为它给了我们一个signed zero,比如IEEE 754 floating point。而且,因为我们使用的是饱和算法,-127 - x == -127
和 127 + x == 127
。
这与浮点无穷大有什么关系?用浮点数替换整数,用NEGATIVE_INFINITY
替换BLACK
,用POSITIVE_INFINITY
替换WHITE
,你会得到什么? NEGATIVE_INFINITY - x == NEGATIVE_INFINITY
和 POSITIVE_INFINITY + x == POSITIVE_INFINITY
。
既然你用了POSITIVE_INFINITY
,那我也用它。首先,我们需要一个类来表示基于整数的饱和颜色;让我们称它为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
语句并将a
和b
替换为它们的值(值?):
System.out.println(127 == 127);
System.out.println(127 < 127);
System.out.println(127 > 127);
打印出来的内容应该很明显了。
【讨论】:
饱和算术和无穷大之间的一个重要区别是,在大多数饱和算术系统中,饱和值在后续运算中被视为非饱和值,因此 120+10-20 将产生 107 而不是比 110 或 127。相比之下,对无穷大的运算只会产生更多的无穷大,除非在少数情况下它们会产生 NaN,或者在将有限数除以无穷大的情况下,会产生正零或负零。 @supercat 正确,这就是类比不成立的地方。【参考方案6】:为什么无穷大是相等的?因为它有效。
浮点运算旨在产生(相对)快速的计算,以保留错误。这个想法是您在冗长的计算过程中不检查溢出或其他废话;你等到它完成。这就是 NaN 以它们的方式传播的原因:一旦你得到一个 NaN,你可以做的事情很少会让它消失。计算完成后,您可以查找 NaN 以检查是否出现问题。
对于无穷大也是如此:如果有溢出的可能性,不要做会丢弃无穷大的事情。
如果您想缓慢而安全,IEEE-754 具有安装陷阱处理程序的机制,以便在计算结果为 NaN 或无穷大时向您的代码提供回调。大多数情况下没有使用。一旦代码被正确调试,它通常太慢而且毫无意义(不是那么容易:人们获得了如何做好这件事的博士学位)。
【讨论】:
【参考方案7】:因为那是标准。 Infinity 表示大于或小于 Double.MAX_VALUE/-Double.MAX_VALUE 的数字。
NaN 表示没有意义的操作的结果。也就是说,该操作不可能得出一个数字。
我猜逻辑是一旦一个数字变得足够大(无穷大),并且由于浮点数的限制,向其中添加数字不会改变结果,所以它“像”无穷大。
因此,如果您想与非常大的数字进行比较,有时您可能会说这两个大数字对于所有意图和目的来说都足够接近。但是如果你想比较两个都不是数字的东西,你不能比较它们,所以它是错误的。至少你不能把它们当做原始人来比较。
【讨论】:
【参考方案8】:这是因为 NaN 不是数字,因此不等于包括 NaN 在内的任何数字。
【讨论】:
不确定这是否解决了问题。这个问题与为什么无穷大的结果被视为相等有关。虽然无穷大是数字,但为什么 java 必须将两个无穷大视为相等的问题是一个重要的问题。即使答案只是“就是这样”,这也是一个需要理解的重要行为。 对不起,我没有提到原因。它是在 IEEE 754 浮点标准中定义的。 如果 NaN 不是数字,你怎么能说“包括 NaN 在内的任何数字”? 我的意思是说它不等于任何数字。因此,它不是一个数字。从这个角度来看,它不具有可以与数字相媲美的值,并且除了它不是数字之外不具有确定的值。因为它没有确定的价值,所以你不能和它自己比较,因为它没有确定的价值,可以说。以上是关于与 NaN 不同,为啥浮点无穷大是相等的?的主要内容,如果未能解决你的问题,请参考以下文章