2011-01-04 4 views
3

Wenn ich dies in meiner Rails-Anwendung ausführen:Rails Präzision Fehler

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

Und dieser Wert wird zurückgegeben:

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

my_envelope.transactions.sum(:amount) 

Diese SQL in den Protokolldateien angezeigt wird

Wie Sie sehen können, ist der Wert 25.159999. Es sollte 25.16 sein. Wenn ich dieselbe SQL in der Datenbank selbst ausführe, wird der korrekte Wert zurückgegeben.

Ich bin ein wenig verwirrt, weil ich weiß, dass es Präzisionsprobleme mit Floats gibt, aber es gibt ein BigDecimal zurück. Der SQL-Spaltentyp ist dezimal. Ich verwende sqlite3 (3.6.17) und sqlite3-ruby (1.3.2). Irgendwelche Ideen?

Update 1

Hier sind die Ergebnisse, wenn ich diese ausführen, um die SQLite3-Rubin-Schnittstelle direkt mit.

$ 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]] 

Die Klasse dieser Nummer ist Float. Übrigens, die drei Zahlen, die es summiert, sind -40,25, 100 und -34,59.

Update 2

Nach mehr Forschung, es stellt sich heraus, dass dies die sqlite3 funktioniert genau so, wie ist. Es gibt ein Double (wie Ruby Float) an sqlite3-ruby zurück und sqlite3-ruby gibt es einfach als Float an Rails weiter. Dann konvertiert Rails es in BigDecimal, da der Spaltentyp dezimal ist. Vor Ruby 1.9 würde Ruby diese Zahl für uns runden und wir würden das Problem nicht sehen.

+0

Bitte aktualisieren Sie Ihre Frage, um anzugeben, welchen Datenbankserver Sie verwenden und welchen Treiber Sie verwenden, um darauf zuzugreifen. – noodl

+0

Dies ist in meiner Testumgebung mit sqlite3 (3.6.17) und ich verwende sqlite3-ruby (1.3.2). – dontangg

+0

Ich vermute, dass sqlite3-ruby ein Float erzeugt, um den Wert des Aggregats zurückzugeben. Können Sie das Ergebnis in eine Zeichenfolge in der Abfrage umwandeln, um dies zu vermeiden? Ich weiß, dass es keine ideale Lösung ist, aber es wird zumindest helfen, das Problem zu isolieren. – noodl

Antwort

2

Es ist keine elegante Lösung, aber Sie können die Erstellung des Float-Objekts umgehen, indem Sie den Wert Ihrer Aggregatberechnung an TEXT in der Abfrage übergeben. Dies "behebt" den Rundungsfehler. Bitte aktualisieren Sie diese Frage, wenn Sie eine bessere Lösung finden (z. B. durch Patchen des sqlite3-ruby-Treibers).

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

Durch einen String Gießen Sie ermöglicht Active Record BigDecimal Konstruktor aufzurufen, die Floating-Point-Ausgaben einen String und unter Umgehung Float mit ungenauen ISO erfordert.

Übrigens bezweifle ich, dass es eine gute Idee ist, Ihren Tisch transactions zu nennen. Das kann irgendwann mit einem anderen Klassennamen oder einem datenbankspezifischen Schlüsselwort kollidieren.

+0

Vielen Dank für die Hilfe mit diesem. Dies ist eine gute Lösung für den Moment. Ich habe einen Fehler für sqlite3-ruby gemeldet. Wenn ich Zeit habe, kann ich es vielleicht selbst beheben, aber da ich SQLite nicht in der Produktion verwende, werde ich mir jetzt keine Sorgen machen. – dontangg