BigDecimal在1.8对1.9

时间:2012-03-09 23:55:20

标签: ruby bigdecimal

当升级到ruby 1.9时,我在比较BigDecimal的预期值与实际值之间进行了测试失败,这是划分Float的结果。

expected: '0.495E0',9(18)
got:      '0.4950000000 0000005E0',18(27)
谷歌搜索“bigdecimal ruby​​ precision”和“bigdecimal changes ruby​​ 1.9”这样的东西并不能让我随处可见。

ruby​​ 1.9中BigDecimal的行为是如何变化的?

更新1

> RUBY_VERSION
=> "1.8.7"
> 1.23.to_d
=> #<BigDecimal:1034630a8,'0.123E1',18(18)>

> RUBY_VERSION
=> "1.9.3"
> 1.23.to_d
=> #<BigDecimal:1029f3988,'0.123E1',18(45)>

18(18)和18(45)是什么意思?精确度我想,但是符号/单位是什么?

更新2

代码正在运行:

((10 - 0.1) * (5.0/100)).to_d

我的测试期望它等于(==)到:

0.495.to_f

在1.8下通过,在1.9.2和1.9.3下失败

2 个答案:

答案 0 :(得分:17)

平等比较在FP值上很少成功


简短的回答是Float#to_d在1.9中更准确,并且正确地失败了1.8.7中不应该成功的相等测试。

长答案涉及浮点编程的基本规则:从不进行等式比较。建议使用if (abs(x-y) < epsilon)之类的模糊比较,或编写代码以避免需要相等完全比较。

虽然理论上有2个 32 单精度数和2个 64 双精度数可以精确比较,但是有一个无数的不可能是所以比较。 (注意: 可以安全地对FP值进行相等比较,而这些值恰好是积分。所以,与许多建议相反,它们实际上对于循环索引和下标非常安全。)

更糟糕的是,我们编写小数的方式使得与任何特定常数的比较不太可能成功。

那是因为分数是二进制的,即1/2 + 1/4 + 1/8 ......但我们的常数是十进制的。因此,例如,考虑$1.00, $1.01, $1.02 .. $1.99.范围内的货币金额此范围内有100个值,但其中只有4个具有精确的FP表示:1.00, 1.25, 1.50, and 1.75.

所以,回到你的问题。 0.495的结果没有精确的表示,0.1.的输入常量也没有。您可以通过减去两个不同大小的FP数来开始计算。较小的数字将被非规范化以便完成减法,因此它将丢失两个或三个低阶位。结果,计算将导致略大于0.495的数字,因为整数0.1未从10中减去。您的常数实际上略小于(内部)0.495。这就是比较失败的原因。

Ruby 1.8必须意外或故意丢失一些低位,并有效地引入一个最终帮助您进行测试的舍入步骤。

请记住:根据经验,您必须明确编程以进行浮点比较。


注释。要回答关于没有精确表示的简单小数分数常量的注释中的问题:它们没有精确的有限形式,因为它们以二进制重复。每个机器分数是x / 2 n 形式的有理数。现在,常数是十进制的,每个十进制常数是x /(2 n * 5 m )形式的有理数。 5 m 数字是奇数,因此对于它们中的任何一个都没有2 n 因子。只有当m == 0时,才能在分数的二进制和十进制扩展中得到有限的表示。因此,1.25是准确的,因为它是5 /(2 2 * 5 0 )但0.1不是因为它是1 /(2 0 * 5 1 )。 根本没有办法将0.1表示为x / 2 n 组件的有限和。

答案 1 :(得分:1)

请参阅Wikipedia article on floating point accuracy problems。它很好地解释了为什么使用浮点数不能用完全来表示像0.1和0.01这样的数字。

简单的解释是,当以二进制浮点格式表示时,这些数字是重复出现的,就像三分之一是0.3333333333 ...以十进制重复出现。

正如你永远不能完全使用一组有限的十进制数字代表三分之一,你不能使用一组有限的二进制数字来表示这些数字。