4

我正在玩一些rails,我发现了一些奇怪的东西。为了存储货币值,我使用活动记录转换为 BigDecimal 的典型十进制数据类型。我认为这是精确的,并且我想避免浮点数学的奇怪行为。但是当我将 99.99 存储到数据库时,一切正常,但是当记录被活动记录加载时,它会失去精度并转换为 99.9899999999 之类的东西。这看起来像一个浮点问题。

我做了一些测试,发现像这样创建一个 BigDecimal b = BigDecimal.new("99.99") 会导致一个“干净”的变量,但是以这种方式构建它 b = BigDecimal.new(99.99) 会导致一个“不干净”的版本我想避免的。

我猜,ActiveRecord 在从数据库加载记录时使用中间浮点数重构 BigDecimal。这不是我想要的,我想知道是否可以避免。

Ruby 版本 1.9.3p0 Rails 3.2.9 Sqlite 3.7.9

4

2 回答 2

5

您的问题是您使用的是 SQLite,而 SQLite 没有对numeric(m,n)数据类型的本机支持。来自精美手册

1.0 存储类和数据类型

存储在 SQLite 数据库中(或由数据库引擎操作)的每个值都具有以下存储类之一:

  • 无效的。该值为 NULL 值。
  • 整数。该值是一个有符号整数,根据值的大小存储在 1、2、3、4、6 或 8 个字节中。
  • 真实的。该值是一个浮点值,存储为一个 8 字节的 IEEE 浮点数。
  • 文本。该值是一个文本字符串,使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储。
  • 斑点。该值是一团数据,完全按照输入的方式存储。

进一步阅读该页面以了解 SQLite 的类型系统是如何工作的。

您的 99.99 可能BigDecimal.new('99.99')在您的 Ruby 代码中,但几乎可以肯定它是 SQLite 中的REAL99.99(即 8 字节 IEEE 浮点值),并且附近有。

所以在你的开发环境中切换到更好的数据库;特别是,在您将要部署的任何数据库之上进行开发。

于 2012-12-12T00:11:07.590 回答
2

不要将浮点数用于货币值

是的,确切地说,SQLite 正在弄乱您的 BigDecimal 值。

根本问题是 FP 格式无法正确存储大多数小数。

我相信你有四个选择:

  1. 将所有内容四舍五入到小数点后两位,这样您就不会注意到稍微偏离的值。
  2. 使用 TEXT 或 BLOB 存储类将 BigDecimal 值存储在 SQLite 中。
  3. 使用具有某种十进制字符串支持的不同数据库。
  4. 将所有内容缩放为整数值并使用 INTEGER 存储类。

问题是 FP 分数是 x/2 n形式的有理数。但十进制货币金额的分数为 x/(2 n * 5 m )。表示只是不兼容。例如,在 0.01 ... 0.99 中,只有 0.25、0.50 和 0.75 具有精确的二进制表示。

于 2012-12-12T01:29:43.263 回答