Rails通过抛出浮点逻辑来搞乱BigDecimals

时间:2012-12-11 23:42:08

标签: ruby-on-rails ruby sqlite floating-point bigdecimal

我正在玩铁轨,我发现了一些奇怪的东西。为了存储货币值,我使用典型的十进制数据类型,活动记录转换为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

2 个答案:

答案 0 :(得分:5)

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

  

1.0存储类和数据类型

     

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

     
      
  • NULL。该值为NULL值。
  •   
  • INTEGER。该值是有符号整数,存储为1,2,3,4,6或8个字节,具体取决于值的大小。
  •   
  • REAL。该值为浮点值,存储为8字节IEEE浮点数。
  •   
  • TEXT。该值是一个文本字符串,使用数据库编码(UTF-8,UTF-16BE或UTF-16LE)存储。
  •   
  • BLOB。该值是一团数据,与输入完全一致。
  •   

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

您的Ruby代码中的99.99可能是BigDecimal.new('99.99'),但几乎可以肯定的是SQLite内部的 REAL 99.99(即一个8字节的IEEE浮点值)走到附近。

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

答案 1 :(得分:2)

不要将浮点用于货币值

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

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

我相信你有四个选择:

  1. 将所有内容舍入到两个小数位,这样您就不会注意到略微偏离的值。
  2. 使用TEXT或BLOB存储类将您的BigDecimal值存储在SQLite中。
  3. 使用具有某种十进制字符串支持的其他数据库。
  4. 将所有内容扩展为整数值并使用INTEGER存储类。
  5. 问题是FP分数是x / 2 n 形式的有理数。但十进制货币金额的分数为x /(2 n * 5 m )。表示只是不兼容。例如,在0.01 ... 0.99中,只有0.25,0.50和0.75具有精确的二进制表示。