BigDecimal在1.8对1.9

Joh*_*hir 5 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"之类的东西并没有让我感到满意.

BigDecimal红宝石1.9中的行为是如何变化的?

更新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)>
Run Code Online (Sandbox Code Playgroud)

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

更新2

代码正在运行:

((10 - 0.1) * (5.0/100)).to_d
Run Code Online (Sandbox Code Playgroud)

我的测试期望它与(==)相等:

0.495.to_f
Run Code Online (Sandbox Code Playgroud)

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

Dig*_*oss 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 米的数字是奇数,因此其中任何一个都没有2 n因子.只有当m == 0时,才能在分数的二进制和十进制扩展中得到有限的表示.因此,1.25是准确的,因为它是5 /(2 2*5 0),但0.1不是因为它是1 /(2 0*5 1).根本没有办法将0.1表示为x/2 n分量的有限和.