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 …以十进制重复出现。

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