首页 文章

BigDecimal在1.8对1.9

提问于
浏览
5

当升级到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 的行为是如何在ruby 1.9中改变的?

update 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)是什么意思?精确的我想象,但是符号/单位是什么?

update 2

代码正在运行:

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

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

0.495.to_f

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

2 回答

  • 17

    Wikipedia article on floating point accuracy problems . 它非常好地解释了为什么使用浮点数无法准确表示0.1和0.01之类的数字 .

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

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

  • 1

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


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

    长答案涉及浮点编程的基本规则:永远不要进行相等比较 . 相反,建议进行模糊比较,如 if (abs(x-y) < epsilon) ,或编写代码以避免完全相等的比较 .

    虽然理论上有232个单精度数和264个双精度数可以精确比较,但有一个无数的数字无法进行比较 . (注意: is 可以安全地对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 / 2n形式的有理数 . 现在,常量是十进制的,每个十进制常量是x /(2n * 5m)形式的有理数 . 5m的数字是奇数,所以它们中没有2n因子 . 只有当m == 0时,才能在分数的二进制和十进制扩展中得到有限的表示 . 因此,1.25是准确的,因为它是5 /(22 * 50),但0.1不是因为它是1 /(20 * 51) . 根本没有办法将0.1表示为x / 2n分量的有限和 .

相关问题