为啥这个表达式会导致浮点错误?

Posted

技术标签:

【中文标题】为啥这个表达式会导致浮点错误?【英文标题】:Why does this expression cause a floating point error?为什么这个表达式会导致浮点错误? 【发布时间】:2013-12-04 18:04:12 【问题描述】:

所以浮点运算是inexact,但这并不能完全解释这里发生了什么:

[46] pry(main)> a=0.05
=> 0.05
[47] pry(main)> a=a*26.0/65
=> 0.02

所以在这里我们得到了我们所期望的,我们得到了正确的答案,世界不断地美丽转动。但是我们后来重写了这个函数,当我们这样做时,我们将a=a*26.0/65 换成a*=26.0/65 这不是很好,我们少输入了一个字符!让我们看看效果如何?

[48] pry(main)> a=0.05
=> 0.05
[49] pry(main)> a*=26.0/65
=> 0.020000000000000004
[50] pry(main)> 26.0/65
=> 0.4

说明a*=b不等于写a=a*b。这似乎不是一个正常的浮点舍入错误,因为这些数字都不应该作为浮点数舍入(尾数对于 26.0、26.0/65、65.0 中的每一个都应该足够长)

我确信引擎盖下发生了一些微妙的事情,并且想知道发生了什么?

【问题讨论】:

使用 ruby​​ 2.0.0p247 和 1.9.3p392 [x86_64-linux] 复制。我喜欢“世界不断转动美丽”的部分:) 【参考方案1】:

浮点格式的有效位有足够的位来表示 26/65 是不正确的。 (“有效位”是首选术语。有效位是线性的。尾数是对数的。)

二进制浮点数的有效位是二进制整数。该整数根据指数进行缩放。为了在二进制浮点中表示 26/65,即 0.4,我们必须将其表示为整数乘以 2 的幂。例如,0.4 的近似值是 1•2-1 = .5。更好的近似值是 3•2-3=.375。更好的是 26•2-4 = .40625。

但是,无论您使用什么整数作为有效数字或使用什么指数,这种格式永远不可能是 0.4。假设您有 .4 = f•2e,其中 fe是整数。那么 2/5 = f•2e,所以 2/(5f) = 2e,然后 1/(5f) = 2e-1 和 5f = 21-e。要做到这一点,5 必须是 2 的幂。不是,所以你不能有 .4 = f•2e

在 IEEE-754 64 位二进制浮点中,有效位有 53 位。这样,最接近 0.4 的可表示值是 0.40000000000000002220446049250313080847263336181640625,等于 3602879701896397•2-53

现在让我们看看您的计算。在a=0.05 中,0.05 被转换为浮点数,产生 0.05000000000000000277555756156289135105907917022705078125。

a*26.0/65 中,首先评估a*26.0。精确的数学结果四舍五入到最接近的可表示值,产生 1.3000000000000000444089209850062616169452667236328125。然后除以 65。同样,答案是四舍五入,产生 0.0200000000000000004163336342344337026588618755340576171875。当 Ruby 打印这个值时,它显然认为它足够接近 .02 以至于它只能显示“.02”而不是完整的值。这是合理的,如果您将打印值 .02 转换回浮点数,您将再次获得实际值 0.0200000000000000004163336342344337026588618755340576171875。所以“.02”在某种意义上是0.0200000000000000004163336342344337026588618755340576171875的一个很好的代表。

在您的替代表达式中,您有a*=26.0/65。在此,首先评估 26.0/65。这产生 0.40000000000000002220446049250313080847263336181640625。 这与第一个表达式不同,因为您以不同的顺序执行了操作,因此舍入了不同的数字。可能发生的情况是,第一个表达式中的一个值被四舍五入,而这个不同的值,因为它相对于以浮点表示的值恰好落在哪里,所以向上舍入。

然后将该值乘以a。这将产生 0.020000000000000000388578058618804789148271083831787109375。请注意,该值比第一个表达式的结果更远离 .02。您的 Ruby 实现知道这一点,因此它确定打印“.02”不足以准确表示它。相反,它显示更多数字,显示 0.020000000000000004。

【讨论】:

+1 尊重!很好的解释。我有线索,但你说得像水一样清楚。 请问您是从哪里获得所有这些结果的精确值的?您是否使用了计算或执行计算的某些工具?如果你使用 ruby​​,你是如何让它显示所有数字的?谢谢。【参考方案2】:

我想我明白这里发生了什么。看看这段代码和操作顺序:

irb(main):001:0> a=0.05
=> 0.05
irb(main):002:0> b=26.0
=> 26.0
irb(main):003:0> c=65
=> 65
irb(main):004:0> a*b/c
=> 0.02
irb(main):005:0> a*(b/c)
=> 0.020000000000000004

在这里,a*b/c 是解释器应该如何评估您的表达式a=a*26.0/65。它评估右侧,然后将结果分配给分配的左侧。

现在,运算符 *= 到底是做什么的?如果我们强制修改a*(b/c) 操作的顺序,上面的代码会显示你得到的结果,然后a*=b/c 所以在幕后我认为Ruby 的*= 计算表达式的左侧,然后将其乘以右侧,然后将其分配给右侧。

在我看来,这就是正在发生的事情。 Ruby 的解释器正在修改执行评估的方式,当然,因为我们正在处理非精确的浮点数,这会对结果产生很大的影响,正如 Jon Skeet 在他对这个问题的惊人回答中所解释的那样:Why does changing the sum order returns a different result?

希望这会有所帮助!

【讨论】:

好的,很确定你是对的 [63] pry(main)> 0.05*0.4 => 0.020000000000000004 但我仍然很困惑,根据我对浮点运算的了解,这不应该发生,两种表示都应该有足够的位来代表数字?我记得总和顺序很重要,但看不到这在这里如何应用? (再次感谢您的出色回答) 我明白你的意思。让我们等待,如果有人比我有更多的浮动算术背景可以解开这个谜题。我只是假设没关系,因为使用浮点运算我总是为最坏的情况做好准备:)

以上是关于为啥这个表达式会导致浮点错误?的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript - 为啥包含括号会导致三元表达式错误?

为啥我会收到“浮点异常(核心转储)”?

为啥运行时表达式会导致 Entity Framework Core 5 的缓存发生冲突?

为啥这个计算(除法)会返回错误的结果?

Cython 将双复数返回到浮点复数导致表达式不在纯 C 中

SQL中 为啥要避免在where后使用'1=1'这种表达式作为部分条件