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

时间:2013-11-20 16:17:01

标签: ruby floating-point

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

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

所以在这里我们得到了我们的期望,我们得到正确的答案,世界不断变化。但是我们后来重写了这个函数,当我们这样做时,我们交换a=a*26.0/65a*=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中的每一个都应该足够长)

我确信引擎盖下有一些微妙的内容,想知道发生了什么事?

2 个答案:

答案 0 :(得分:5)

浮点格式的有效位数不足以表示26/65。 (“重要”是首选术语。重要性是线性的.Mantissas是对数的。)

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

但是,无论你使用什么整数来表示有效数字或你使用什么指数,这种格式永远不会完全正确.4。假设你有.4 = f •2 e ,其中 f e 是整数。然后2/5 = f •2 e ,所以2 /(5 f )= 2 e ,然后1 /(5 f )= 2 e -1 和5 < i> f = 2 1- e 。要做到这一点,5必须是2的力量。它不是,所以你不能有.4 = f •2 e

在IEEE-754 64位二进制浮点中,有效位数为53位。有了这个,最接近.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.02000000000000000388578058618804789148271083831787109375。请注意,此值比第一个表达式的结果更远离.02。你的Ruby实现知道这一点,所以它确定打印“。02”不足以准确地表示它。相反,它显示更多数字,显示0.020000000000000004。

答案 1 :(得分:3)

我想我得到了这里发生的事情。看看这段代码和操作顺序:

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在他对这个问题的惊人回答中解释道:{{3} }

希望这有帮助!