BigDecimal在1.8对1.9
当升级到ruby 1.9时,我在比较BigDecimal
预期值与实际值时遇到了失败的测试, BigDecimal
是划分Float的结果。
预期:'0.495E0',9(18) 得到:'0.4950000000 0000005E0',18(27)
谷歌搜索“bigdecimal ruby precision”和“bigdecimal changes ruby 1.9”这样的东西并没有让我到处都是。
BigDecimal
的行为是如何在ruby 1.9中改变的?
更新1
> RUBY_VERSION => "1.8.7" > 1.23.to_d => # > RUBY_VERSION => "1.9.3" > 1.23.to_d => #
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下失败
平等比较在FP值上很少成功
简短的回答是Float#to_d
在1.9中更准确,并且正确地失败了1.8.7中不应该成功的相等测试。
长答案涉及浮点编程的基本规则: 永远不要进行相等比较。 相反,建议使用if (abs(xy) < epsilon)
等模糊比较,或编写代码以避免完全相等的比较。
虽然理论上可以精确地比较2 32个单精度数和2 64个双精度数,但是有一个无数的数字无法进行比较。 (注意:对FP值进行相等比较是安全的,这恰好是积分。因此,与许多建议相反,它们对于循环索引和下标实际上是非常安全的。)
更糟糕的是,我们编写小数的方式使得与任何特定常数的比较不太可能成功。
这是因为分数是二进制的,即1/2 + 1/4 + 1/8 ......但我们的常数是十进制的。 因此,例如,考虑$1.00, $1.01, $1.02 .. $1.99.
范围内的货币金额$1.00, $1.01, $1.02 .. $1.99.
此范围内有100个值,但只有4个具有精确的FP表示forms: 1.00, 1.25, 1.50, and 1.75.
所以,回到你的问题。 您的0.495
结果没有精确的表示,输入常数也不是0.1.
您可以通过减去两个不同大小的FP数来开始计算。 较小的数字将被非规范化以便完成减法,因此它将丢失两个或三个低位。 结果,计算将导致略大于0.495的数字,因为整数0.1不从10中减去。您的常数实际上略小于(内部)0.495。 这就是比较失败的原因。
Ruby 1.8必须偶然或故意丢失一些低位,并有效地引入一个舍入步骤,最终帮助您进行测试。
请记住:经验法则是您必须在这种舍入中明确编程以进行浮点比较。
笔记。 要回答关于没有精确表示的简单小数分数常量的注释中的问题:它们没有精确的有限forms,因为它们以二进制重复。 每个机器分数是x / 2 nforms的有理数。 现在,常量是十进制的,每个十进制常数是x /(2 n * 5 m )forms的有理数。 5 米的数字是奇数,因此其中任何一个都没有2 n因子。 只有当m == 0时,才能在分数的二进制和十进制扩展中得到有限的表示。 因此,1.25是准确的,因为它是5 /(2 2 * 5 0 ),但0.1不是因为它是1 /(2 0 * 5 1 )。 根本没有办法将0.1表示为x / 2 n分量的有限和。
请参阅Wikipedia关于浮点精度问题的文章 。 它非常好地解释了为什么使用浮点数无法准确表示0.1和0.01之类的数字。
简单的解释是,当以二进制浮点格式表示时,这些数字是重复的,就像三分之一是0.3333333333 …以十进制重复出现。
就像你永远不能完全使用一组有限的十进制数字代表三分之一,你不能使用一组有限的二进制数字来表示这些数字。