Rails精度错误

时间:2011-01-04 07:26:18

标签: ruby-on-rails precision

当我在我的Rails应用程序中运行它时:

my_envelope.transactions.sum(:amount)

此SQL显示在日志文件中:

SQL (0.3ms)  SELECT SUM("transactions"."amount") AS sum_id FROM "transactions" WHERE (envelope_id = 834498537)

返回此值:

<BigDecimal:1011be570,'0.2515999999 9999997E2',27(27)>

如您所见,该值为25.159999。它应该是25.16。当我自己在数据库上运行相同的SQL时,会返回正确的值。

我有点困惑,因为我知道Floats存在精确问题,但它返回了一个BigDecimal。 SQL列类型是十进制。我正在使用sqlite3(3.6.17)和sqlite3-ruby(1.3.2)。有什么想法吗?

更新1

以下是使用SQLite3-ruby界面直接运行时的结果。

$ rails c test
Loading test environment (Rails 3.0.3)
irb(main):001:0> db = SQLite3::Database.new("db/test.sqlite3")
=> #<SQLite3::Database:0x5242020>
irb(main):002:0> db.execute("SELECT SUM(amount) FROM transactions WHERE envelope_id = 834498537")
=> [[25.159999999999997]]

该号码的类是Float。顺便说一句,它总和的三个数字是-40.25,100和-34.59。

更新2

经过更多的研究,结果证明这就是sqlite3的工作方式。它返回一个double(与Ruby Float相同)到sqlite3-ruby,而sqlite3-ruby只是将它作为Float传递给Rails。然后,Rails将其转换为BigDecimal,因为列类型是十进制的。在Ruby 1.9之前,Ruby会为我们舍入这个数字,我们不会看到问题。

1 个答案:

答案 0 :(得分:2)

这不是一个优雅的解决方案,但您可以通过将聚合计算的值转换为查询中的TEXT来绕过Float对象的创建。这“修复”舍入错误。如果您找到更好的解决方案(例如通过修补sqlite3-ruby驱动程序),请更新此问题。

SELECT CAST(SUM(amount) AS TEXT) FROM transactions WHERE envelope_id = 834498537

通过强制转换为字符串,您可以允许Active Record调用BigDecimal的构造函数,该构造函数需要一个字符串并绕过Float,其中包含不准确的ISO浮点问题。

顺便说一句,我怀疑为你的表transactions命名是个好主意。在某些时候,这肯定会与其他一些类名或特定于数据库的关键字发生冲突。