为什么BigDecimal返回一个奇怪的值?

时间:2009-04-23 18:32:14

标签: ruby floating-point bigdecimal

我正在编写处理货币,收费等的代码。我将使用BigDecimal类进行数学和存储,但我们遇到了一些奇怪的事情。

本声明:

1876.8 == BigDecimal('1876.8')

返回false。

如果我通过格式化字符串"%.13f"运行这些值,我会得到:

"%.20f" % 1876.8 => 1876.8000000000000
"%.20f" % BigDecimal('1876.8') => 1876.8000000000002

请注意最后一个小数位的BigDecimal中的额外2

我认为BigDecimal应该能够抵消直接在计算机的本机浮点中存储实数的不准确性。 2来自哪里?

6 个答案:

答案 0 :(得分:9)

它不会给你那么多的小数位数控制,但BigDecimal的传统格式机制似乎是:

a.to_s('F')

如果您需要更多控制权,请考虑使用Money gem,假设您的域名问题主要与货币有关。

gem install money

答案 1 :(得分:8)

你是对的,BigDecimal应该正确存储它,我最好的猜测是:

  • BigDecimal正确存储值
  • 当传递给字符串格式化函数时,BigDecimal将被转换为较低精度的浮点值,从而创建... 02。
  • 当直接与浮点数进行比较时,浮点数有一个额外的小数位,远远超过你看到的20(经典浮点数不能比较行为)。

无论哪种方式,您都不可能获得将浮点数与BigDecimal进行比较的准确结果。

答案 2 :(得分:7)

不要将FPU十进制字符串分数与等式进行比较

问题在于浮点值或双精度值与包含分数的十进制常量的相等比较很少成功。

很少有十进制字符串分数在二进制FP表示中具有精确值,因此相等比较通常是注定的。 *

要回答您的确切问题,2来自稍微不同的十进制字符串分数转换为Float格式。由于无法精确表示分数,因此两次计算可能会在中间计算中考虑不同的精度,最终会将结果舍入为52位IEEE 754双精度尾数。它几乎不重要,因为 无论如何都没有确切的表示,但是其中一个可能比另一个更错误。

特别是,1876.8无法由FP对象精确表示,事实上,在0.01和0.99之间,只有0.25,0.50和0.75具有精确的二进制表示。所有其他的,包括1876.8,永远重复,并舍入到52位。这大约是BigDecimal存在的原因的一半。 (另一半原因是FP数据的固定精度:有时你需要更多。)

因此,在将实际机器值与十进制字符串常量进行比较时得到的结果取决于二进制分数中的每个位...低至1/2 52 ......即使这样也需要四舍五入。

如果对于产生数字,输入转换代码或其他任何相关内容的过程有任何甚至一点点(hehe, bit,抱歉)不完美,它们都不会显示完全相同。

甚至可以使比较 总是失败,因为没有IEEE格式的FPU甚至可以准确地表示该数字。它们真的不相等,即使它们看起来像它。在左侧,您的十进制字符串已转换为二进制字符串,并且大多数数字不会完全转换。在右边,它仍然是一个十进制字符串。

所以不要将浮点数与BigDecimal混合,只需将一个BigDecimal与另一个BigDecimal进行比较。 (即使两个操作数浮动,对等式的测试也需要非常小心或模糊测试。另外,不要相信每个格式化的数字:输出格式将在分数的右侧带有余数,所以你通常不会看到零,你只会看到垃圾值。)


* 问题:机器编号为x / 2 n ,但十进制常数为x /(2 n * 5 )。你作为符号,指数和尾数的值是无限重复的0 10000001001 1101010100110011001100110011001100110011001100110011...具有讽刺意味的是,FP算术非常精确,当值没有分数时,相等比较可以很好地工作。

答案 3 :(得分:3)

正如David所说,BigDecimal正确地存储它

 p (BigDecimal('1876.8') * 100000000000000).to_i

返回187680000000000000

所以,是的,字符串格式化正在破坏它

答案 4 :(得分:2)

如果您不需要小数分数,请考虑将货币存储和操作为整数,然后在显示时间除以100。我发现这比处理浮点存储和操作的不可避免的精度问题更容易。

答案 5 :(得分:1)

在Mac OS X上,我正在运行ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9]

irb(main):004:0> 1876.8 == BigDecimal('1876.8') => true

然而,作为Ruby,我认为你应该考虑发送给对象的消息。这回报给你的是什么:

BigDecimal('1876.8') == 1876.8

这两个不等同,如果你试图使用BigDecimal的能力来确定精确的十进制相等,它应该是消息的接收者询问相等性。

出于同样的原因,我不认为通过向格式字符串发送格式消息来格式化BigDecimal也是正确的方法。