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

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

[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)

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

浮点格式的有效位数有足够的位来表示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 ,其中fe是整数。 然后2/5 = f ·2 e ,所以2 /(5 f )= 2 e ,然后1 /(5 f )= 2 e -1和5 f = 2 1- e 。 要做到这一点,5必须是2的力量。 它不是,所以你不能有.4 = f •2 e

在IEEE-754 64位二进制浮点中,有效位数为53位。 使用此,与.4最接近的可表示值为0.40000000000000002220446049250313080847263336181640625,等于3602879701896397•2 -53

现在让我们来看看你的计算。 在a=0.050.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。

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

 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在他对这个问题的惊人回答中解释: 为什么要更改sum order返回不同的结果?

希望这可以帮助!