Ruby 的 BigDecimal 类中的浮点错误?[复制]

floating point error in Ruby's BigDecimal class? [duplicate]

提问人:Phil R 提问时间:7/21/2016 最后编辑:Mladen JablanovićPhil R 更新时间:7/21/2016 访问量:338

问:

避免 ruby 中浮点错误的通用建议是使用 BigDecimal。我一定忽略了什么,因为我想我发现了一个 BigDecimal 数学返回错误的情况,而 Float 没有:

使用 Float 给出的正确答案是 2.75:

> 50.0 * 0.6 / 360.0 * 33
=> 2.75

使用 BigDecimal 给出了 2.74999999 的错误答案:

> BigDecimal("50") * BigDecimal("0.6") / BigDecimal("360") * BigDecimal("33")
=> #<BigDecimal:7efe74824c80,'0.2749999999 999999989E1',27(36)>

有人请告诉我我在这里错过了什么?

Ruby Bigdecimal 浮点精度

评论

0赞 Mladen Jablanović 7/21/2016
你的问题是为什么BigDecimal会给出错误,或者为什么Float不会?
0赞 Phil R 7/21/2016
为什么BigDecimal会给出错误?这个例子是否意味着我们不能再信任 BigDecimal 进行浮点计算?
1赞 the Tin Man 7/21/2016
请参见 stackoverflow.com/q/588004/128421。你误解了浮点数学。
0赞 Rudy Velthuis 7/23/2016
FWIW,如果可以的话,先乘法再除法:你会得到你想要的结果。@Mladen 的答案已经告诉你,如果你按照自己的方式去做,为什么你没有得到确切的结果。如果你先乘法,你得到确切结果的机会就更高。上面的行 (或 ),它不会产生任何重复的小数。BigDecimal("50") * BigDecimal("0.6") * BigDecimal("33") / BigDecimal("360")990 / 360(11 * 90) / (4 * 90)

答:

3赞 Mladen Jablanović 7/21/2016 #1

让我们简化您的示例,并改用以下示例:

BigDecimal(1) / BigDecimal(3) * BigDecimal(3)
# => #<BigDecimal:19289d8,'0.9999999999 99999999E0',18(36)>

它是如何到达那里的?

BigDecimal(1) / BigDecimal(3)
# => #<BigDecimal:1921a70,'0.3333333333 33333333E0',18(36)>

BigDecimal 不提供有理数,所以当你把 1 除以 3 时,你会得到 0,后面跟着很多 3。很多,但不是无限多。当你把它乘以 3 时,你会得到 0,然后是同样多的 9。

我相信你误读了 BigDecimal 的广告(尽管我不确定它是否在任何地方被宣传为浮点错误的解决方案)。它只是提供任意精度。它仍然是一个浮点数。如果你真的想要在除数时精确的数字,你可以看看类:Rational

(Rational(50) * Rational(0.6) / Rational(360) * Rational(33)).to_f
# => 2.75

评论

0赞 cremno 7/21/2016
从 2.1 开始,可以使用后缀:。r50r * Rational(0.6) / 360r * 33r
0赞 Phil R 7/21/2016
谢谢姆拉登。我正在编写一个金融应用程序,并考虑了以下所有类型来表示货币: * 整数,以美分为单位存储金额:不好,因为我们需要能够存储一美分的分数。* Money gem:使用整数,所以和整数一样的问题。* 浮点数:容易出现浮点错误。* BigDecimal:不太容易出现浮点错误(但现在我发现,在我们的一个用例中仍然容易出错!也许理性就是答案。
1赞 Phil R 7/21/2016
另一个似乎对我有用的解决方案: BigDecimal(“50”) * BigDecimal(“0.6”) * BigDecimal(“33”) / BigDecimal(“360”) 这是基于线程中的一条评论,Jordan 将其标记为重复项: 实际上,准确进行此类计算的关键不是使用定点算术,而是安排计算以避免必须对将要放大的值进行四舍五入。例如,要向某人收取 30 天月中 17 天的费用,请将每月金额乘以 17,然后除以 30,而不是除以 30 再乘以 17。– 超级猫 Oct 1 '14 at 15:13