6

所以这很奇怪。我在 Ruby 1.9.3 中,浮点加法没有像我预期的那样工作。

0.3 + 0.6 + 0.1 = 0.9999999999999999
0.6 + 0.1 + 0.3 = 1

我已经在另一台机器上尝试过这个并得到相同的结果。知道为什么会发生这种情况吗?

4

5 回答 5

12

浮点运算是不精确的:它们将结果四舍五入到最接近的可表示浮点值。
这意味着每个浮动操作是:

float(a op b) = mathematical(a op b) + rounding-error( a op b )

如上式所示,舍入误差取决于操作数 a 和 b。
因此,如果您以不同的顺序执行操作,

float(float( a op b) op c) != float(a op (b op c))

换句话说,浮点运算不是关联的。
虽然它们是可交换的......

正如其他人所说,将十进制表示 0.1(即 1/10)转换为基数 2 表示(即 1/16 + 1/64 + ... )将导致无限的数字系列。所以 float(0.1) 不完全等于 1/10,它也有一个舍入误差,它会导致一长串二进制数字,这说明以下操作有一个非空舍入误差(数学结果不可表示)浮点数)

于 2012-12-12T20:56:07.037 回答
6

之前已经说过很多次了,但需要重复一遍:浮点数本质上是十进制数的近似值。由于浮点数以二进制存储的方式,有些十进制数无法精确表示。出现小但可察觉的舍入误差。

为了避免这种混乱,您应该始终将您的数字格式化为适当数量的展示位置:

 '%.3f' % (0.3 + 0.6 + 0.1)
 # => "1.000" 

 '%.3f' % (0.6 + 0.1 + 0.3)
 # => "1.000" 

这就是为什么将浮点数用于货币值是有风险的,并且通常鼓励您使用定点数或常规整数来表示这些东西。

于 2012-12-12T20:25:01.970 回答
3

首先,将源文本中的数字“0.3”、“.6”和“.1”转换为浮点数,我将其称为abc。这些值接近 0.3、0.6 和 0.1,但不等于它们,但这并不是您看到不同结果的直接原因。

在每个浮点算术运算中,可能会有一点舍入误差,一些小的数字e i。因此,您的两个表达式计算的确切数学结果是:

( a + b + e 0 ) + c + e 1和 ( b + c + e 2 ) + a + e 3

也就是说,在第一个表达式中,a被添加到b中,并且存在轻微的舍入误差e 0。然后加上c,有轻微的舍入误差e 1。在第二个表达式中,b被添加到c中,并且存在轻微的舍入误差e 2。最后加上a,有轻微的舍入误差e 3

您的结果不同的原因是e 0 + e 1e 2 + e 3。即,添加ab时所需的舍入与添加bc时所需的舍入不同和/或两种情况的第二次添加所需的舍入不同。

有一些规则可以控制这些错误。如果您知道规则,则可以对它们进行推断,以限制最终结果中的错误大小。

于 2012-12-12T21:14:07.560 回答
2

这是浮点数的一个常见限制,因为它们是以基数 2 而不是基数 10 编码的。这可能很难理解,但一旦你这样做了,你就可以轻松避免这样的问题。我推荐这个指南,它深入解释它。

对于这个问题,您可以尝试将结果四舍五入到最接近的百万分之一位:

result = (0.3+0.6+0.1)
  => 0.9999999999999999
(result*1000000.0).round/1000000.0
  => 1.0

至于为什么顺序很重要,它与四舍五入有关。当这些数字转换为浮点数时,它们将转换为二进制,并且它们都变成重复的分数,就像 ⅓ 是十进制一样。由于在每次加法过程中结果都会四舍五入,因此最终答案取决于加法的顺序。似乎在其中一个中,你得到了一个回合,而在另一个中,你得到一个回合。这解释了差异。

值得注意的是这两个答案之间的实际差异是什么:大约 0.0000000000000001。

于 2012-12-12T20:29:26.823 回答
0

鉴于您还可以使用number_with_precision助手:

result = 0.3 + 0.6 + 0.1
result = number_with_precision result, :precision => 3
于 2012-12-12T20:38:44.440 回答