为啥这个表达式会导致浮点错误?
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,其中 f 和 e是整数。那么 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 的缓存发生冲突?