Float vs Decimal in ActiveRecord

283

Manchmal verwirren mich Activerecord-Datentypen. Ähm, oft. Eine meiner ewigen Fragen ist für einen bestimmten Fall:

Soll ich verwenden :decimaloder :float?

Ich bin oft auf diesen Link gestoßen , ActiveRecord :: decimal vs: float? , aber die Antworten sind nicht klar genug, um sicher zu sein:

Ich habe viele Threads gesehen, in denen Leute empfehlen, niemals float und immer dezimal zu verwenden. Ich habe auch Vorschläge von einigen Leuten gesehen, float nur für wissenschaftliche Anwendungen zu verwenden.

Hier einige Beispielfälle:

  • Geolokalisierung / Breite / Länge: -45.756688, 120.5777777, ...
  • Ratio / Prozentsatz: 0.9, 1.25, 1.333, 1.4143, ...

Ich habe :decimalin der Vergangenheit verwendet, aber ich fand, dass der Umgang mit BigDecimalObjekten in Ruby im Vergleich zu einem Float unnötig umständlich war. Ich weiß auch, dass ich :integerzum Beispiel Geld / Cent darstellen kann, aber es passt nicht ganz für andere Fälle, zum Beispiel wenn sich Mengen, in denen sich die Genauigkeit ändert, im Laufe der Zeit ändern könnten.

  • Was sind die Vor- und Nachteile der Verwendung?
  • Was wären gute Faustregeln, um zu wissen, welcher Typ verwendet werden soll?
Jonathan Allard
quelle

Antworten:

427

Ich erinnere mich, dass mein CompSci-Professor sagte, er solle niemals Floats als Währung verwenden.

Der Grund dafür ist, wie die IEEE-Spezifikation Floats im Binärformat definiert . Grundsätzlich werden Vorzeichen, Brüche und Exponenten gespeichert, um einen Float darzustellen. Es ist wie eine wissenschaftliche Notation für Binär (so etwas wie +1.43*10^2). Aus diesem Grund ist es unmöglich, Brüche und Dezimalstellen genau in Float zu speichern.

Deshalb gibt es ein Dezimalformat. Wenn du das tust:

irb:001:0> "%.47f" % (1.0/10)
=> "0.10000000000000000555111512312578270211815834045" # not "0.1"!

während, wenn Sie nur tun

irb:002:0> (1.0/10).to_s
=> "0.1" # the interprer rounds the number for you

Wenn Sie also mit kleinen Brüchen wie Zinseszinsen oder vielleicht sogar Geolokalisierung zu tun haben, würde ich das Dezimalformat sehr empfehlen, da das Dezimalformat 1.0/10genau 0,1 beträgt.

Es ist jedoch zu beachten, dass Floats trotz ihrer geringeren Genauigkeit schneller verarbeitet werden. Hier ist ein Benchmark:

require "benchmark" 
require "bigdecimal" 

d = BigDecimal.new(3) 
f = Float(3)

time_decimal = Benchmark.measure{ (1..10000000).each { |i| d * d } } 
time_float = Benchmark.measure{ (1..10000000).each { |i| f * f } }

puts time_decimal 
#=> 6.770960 seconds 
puts time_float 
#=> 0.988070 seconds

Antworten

Verwenden Sie float, wenn Sie nicht zu viel Wert auf Präzision legen. Beispielsweise benötigen einige wissenschaftliche Simulationen und Berechnungen nur bis zu 3 oder 4 signifikante Stellen. Dies ist nützlich, um die Genauigkeit gegen die Geschwindigkeit auszutauschen. Da sie weniger Präzision als Geschwindigkeit benötigen, würden sie Float verwenden.

Verwenden Sie Dezimalzahlen, wenn Sie mit Zahlen arbeiten, die präzise sein und zu korrekten Zahlen summiert werden müssen (z. B. Zinseszinsen und geldbezogene Dinge). Denken Sie daran: Wenn Sie Präzision benötigen, sollten Sie immer Dezimalstellen verwenden.

Iuri G.
quelle
Wenn ich das richtig verstehe, ist float in Basis-2, während Dezimalzahl in Basis-10 ist? Was wäre eine gute Verwendung für Float? Was macht Ihr Beispiel und demonstriert es?
Jonathan Allard
1
Meinst du nicht +1.43*2^10lieber als +1.43*10^2?
Cameron Martin
46
Für zukünftige Besucher ist der beste Datentyp für die Währung eine Ganzzahl und keine Dezimalzahl. Wenn die Genauigkeit des Feldes ein paar Cent beträgt, ist das Feld eine ganze Zahl in Cent (keine Dezimalstelle in Dollar). Ich habe in der IT-Abteilung einer Bank gearbeitet und so wurde es dort gemacht. Einige Felder waren präziser (z. B. Hundertstel Cent), aber sie waren immer noch ganze Zahlen.
Adg
1
@adg ist richtig: bigdecimal ist auch eine schlechte Wahl für die Währung.
Eric Duminil
1
@adg du hast recht. Ich habe in den letzten Jahren mit einigen Buchhaltungs- und Finanzanwendungen gearbeitet und wir speichern alle unsere Währungsfelder in ganzzahligen Spalten. In diesen Fällen ist es viel sicherer.
Guilherme Lages Santos
19

In Rails 3.2.18 wird: decimal bei Verwendung von SQLServer zu: integer, in SQLite funktioniert dies jedoch einwandfrei. Der Wechsel zu: float hat dieses Problem für uns gelöst.

Die gewonnene Erkenntnis lautet: "Verwenden Sie immer homogene Entwicklungs- und Bereitstellungsdatenbanken!"

ryan0
quelle
3
Guter Punkt, 3 Jahre später, nachdem ich Rails gemacht habe, stimme ich voll und ganz zu.
Jonathan Allard
3
"Verwenden Sie immer homogene Entwicklungs- und Bereitstellungsdatenbanken!"
zx1986
15

In Rails 4.1.0 hatte ich Probleme beim Speichern von Längen- und Breitengraden in der MySQL-Datenbank. Es kann keine große Bruchzahl mit dem Datentyp float speichern. Und ich ändere den Datentyp auf dezimal und arbeite für mich.

  def ändern
    change_column: Städte ,: Breitengrad ,: Dezimal ,: Genauigkeit => 15 ,: Skala => 13
    change_column: Städte ,: Längengrad ,: Dezimal ,: Genauigkeit => 15 ,: Skala => 13
  Ende
Rokibul Hasan
quelle
Ich speichere meine: Breiten- und Längengrade, während sie in Postgres schweben, und das funktioniert einwandfrei.
Scott W
3
@Robikul: Ja, das ist gut, aber übertrieben. decimal(13,9) ist ausreichend für Breiten- und Längengrade. @ScottW: Ich erinnere mich nicht, aber wenn Postgres IEEE-Floats verwendet, funktioniert es nur "gut", weil Sie noch keine Probleme haben ... NOCH. Es ist ein unzureichendes Format für Längen- und Breitengrade. Yo wird schließlich Fehler in den niedrigstwertigen Ziffern haben.
Lonny Eachus
@LonnyEachus Was macht IEEE Floats für Lat / Longs unzureichend?
Alexander Suraphel
3
@AlexanderSuraphel Wenn Sie dezimale Breiten- und Längengrade verwenden, ist ein IEEE-Float anfällig für Fehler in den niedrigstwertigen Ziffern. So können Ihre Breiten- und Längengrade beispielsweise eine Genauigkeit von 1 Meter haben, aber Sie können Fehler von 100 Metern oder mehr haben. Dies gilt insbesondere dann, wenn Sie sie in Berechnungen verwenden.
Lonny Eachus