当升级到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下失败
答案 0 :(得分:17)
简短的回答是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 ...以十进制重复出现。
正如你永远不能完全使用一组有限的十进制数字代表三分之一,你不能使用一组有限的二进制数字来表示这些数字。