为什么`9.3 == 9.3.to_d`为假?

时间:2018-08-07 10:41:38

标签: ruby floating-point decimal precision

我在TDD期间遇到了一个有趣的案例:

 Failure/Error: expect(MoneyManager::CustomsCalculator.call(price: 31,    weight: 1.12)).to    eq 9.3

   expected: 9.3
        got: 0.93e1

我进一步调查发现:

require 'bigdecimal'
 => true
2.4.2 :005 > require 'bigdecimal/util'
 => true
...
2.4.2 :008 > 1 == 1.to_d
 => true
2.4.2 :009 > 2 == 2.to_d
 => true
2.4.2 :010 > 2.0 == 2.0.to_d
 => true
2.4.2 :011 > 1.3 == 1.3.to_d
 => true
2.4.2 :012 > 9.3 == 9.3.to_d
 => false

为什么9.3 == 9.3.to_d false

PS,我很清楚Float和BigDecimal是什么,但是我对这种特殊的行为感到困惑。

2 个答案:

答案 0 :(得分:3)

这并不是一个真正的“红宝石问题”。这是数字的浮点表示形式问题。

您不能可靠地执行浮点数与“精确”值(由BigDecimal表示)之间的相等性检查。

BigDecimal.new(9.3, 2)是正确的。 9.3不是。

9.3 * 100 #=> 930.0000000000001
1.3 * 100 #=> 130.0

这就是二进制浮点数的工作方式。它们(有时)是“真”值的不精确表示。

您可以:

  • 按原样比较(bigdecimal1 == bigdecimal2float1 == float2)。但也请注意,如果您执行不同的计算以获得这些值,则比较float1 == float2也是不可靠的!或者,
  • 在误差范围内检查值是否等于 (例如,以rspec的术语,expect(value1).to be_within(1e-12).of(value2))。

答案 1 :(得分:1)

由于上述Eric评论而进行了编辑

您可以使用float的性质并将其与您建议的限制进行比较,该限制将可靠地返回stratifytrue

false

在您的示例中,这是(我添加了()以提高可读性):

(bigdecimal-float).abs < comparison_limit

哪个产生((9.3.to_d)-9.3).abs < 0.000001 <-- watch out for the limit! 并可以用于测试。

编辑基于Eric的评论(谢谢您)。 比较两个数字时,务必检查公差极限。

您可以通过以下方式进行操作:

true

这会给你

9.3.next_float

所以你的容忍度应该是

9.300000000000002

注意:注意步骤:

0.000000000000002

现在代码看起来不同:

9.3.next_float.next_float
=> 9.300000000000004