15

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

这个说法:

1876.8 == BigDecimal('1876.8')

返回假。

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

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

请注意 BigDecimal 中最后一位小数的额外2内容。

我认为 BigDecimal 应该可以解决将实数直接存储在计算机的本机浮点中的不准确性。这是2从哪里来的?

4

6 回答 6

9

它不会让您对小数位数进行太多控制,但 BigDecimal 的传统格式机制似乎是:

a.to_s('F')

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

gem install money
于 2009-04-23T21:05:33.617 回答
8

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

  • BigDecimal 正确存储值
  • 当传递给字符串格式化函数时,BigDecimal 被转换为较低精度的浮点值,从而创建 ...02。
  • 直接与浮点数比较时,浮点数的小数位数远远超出您看到的 20 位(经典浮点数无法比较行为)。

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

于 2009-04-23T18:57:41.007 回答
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 ……甚至需要四舍五入。

如果产生数字的过程、输入转换代码或其他任何涉及的过程有任何不完美的地方(呵呵,有点,抱歉),它们看起来不会完全相等。

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

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


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

于 2012-12-25T19:29:25.293 回答
3

正如大卫所说,BigDecimal 正确存储它

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

返回 187680000000000000

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

于 2009-04-23T19:01:35.303 回答
2

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

于 2009-04-23T20:32:14.353 回答
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 也不是正确的方法。

于 2009-04-23T19:21:02.527 回答