Zmienne vs dziesiętne w ActiveRecord

283

Czasami mylą mnie typy danych Activerecord. Err, często. Jednym z moich wiecznych pytań jest, w danym przypadku,

Czy powinienem użyć :decimallub :float?

Często natrafiam na ten link, ActiveRecord:: dziesiętny vs: zmiennoprzecinkowy? , ale odpowiedzi nie są wystarczająco jasne, aby mieć pewność:

Widziałem wiele wątków, w których ludzie zalecają płaskie, aby nigdy nie używali liczb zmiennoprzecinkowych i zawsze używali liczb dziesiętnych. Widziałem także sugestie niektórych osób, aby używać pływaka tylko do zastosowań naukowych.

Oto kilka przykładowych przypadków:

  • Geolokalizacja / szerokość / długość geograficzna: -45.756688, 120.5777777...
  • Stosunek / odsetek: 0.9, 1.25, 1.333, 1.4143, ...

Używałem :decimalw przeszłości, ale okazało się, że radzenie sobie z BigDecimalobiektami w Ruby było niepotrzebnie niewygodne w porównaniu do float. Wiem też, że mogę :integerna przykład przedstawiać pieniądze / centy, ale to nie pasuje do innych przypadków, na przykład gdy ilości, w których precyzja może się zmieniać w czasie.

  • Jakie są zalety / wady korzystania z nich?
  • Jakie byłyby pewne dobre zasady, aby wiedzieć, jakiego rodzaju użyć?
Jonathan Allard
źródło

Odpowiedzi:

427

Pamiętam, jak mój profesor CompSci powiedział, żeby nigdy nie używać liczb zmiennoprzecinkowych dla waluty.

Powodem tego jest to, jak specyfikacja IEEE definiuje zmiennoprzecinkowe w formacie binarnym. Zasadniczo przechowuje znak, ułamek i wykładnik reprezentujący zmiennoprzecinkowe. To jest jak naukowy zapis binarny (coś w stylu +1.43*10^2). Z tego powodu nie można dokładnie przechowywać ułamków zwykłych i dziesiętnych we Float.

Dlatego istnieje format dziesiętny. Jeśli to zrobisz:

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

podczas gdy po prostu zrobisz

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

Więc jeśli masz do czynienia z małymi ułamkami, takimi jak mieszanie interesów, a może nawet geolokalizacja, bardzo polecam format dziesiętny, ponieważ w formacie dziesiętnym 1.0/10jest dokładnie 0,1.

Należy jednak zauważyć, że pomimo mniejszej dokładności, zmiennoprzecinkowe są przetwarzane szybciej. Oto punkt odniesienia:

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

Odpowiedź

Użyj pływaka, gdy nie zależy ci zbytnio na precyzji. Na przykład niektóre symulacje naukowe i obliczenia wymagają tylko 3 lub 4 cyfr znaczących. Jest to przydatne w zamianie na dokładność prędkości. Ponieważ nie potrzebują tak dużej precyzji, jak prędkości, użyliby pływaka.

Użyj liczb dziesiętnych, jeśli masz do czynienia z liczbami, które muszą być precyzyjne i zsumować do poprawnej liczby (np. Składanie odsetek i rzeczy związanych z pieniędzmi). Pamiętaj: jeśli potrzebujesz precyzji, zawsze powinieneś używać liczb dziesiętnych.

Iuri G.
źródło
Więc jeśli dobrze rozumiem, liczba zmiennoprzecinkowa jest w bazie-2, podczas gdy liczba dziesiętna jest w bazie-10? Jaki byłby dobry użytek dla float? Czym zajmuje się Twój przykład?
Jonathan Allard,
1
Nie masz na myśli +1.43*2^10raczej niż +1.43*10^2?
Cameron Martin
47
Dla przyszłych użytkowników najlepszym typem danych dla waluty jest liczba całkowita, a nie dziesiętna. Jeśli dokładność pola to grosze, to pole będzie liczbą całkowitą w groszach (nie dziesiętną w dolarach). Pracowałem w dziale IT banku i tak to tam zrobiono. Niektóre pola były bardziej precyzyjne (np. Setne centa), ale wciąż były liczbami całkowitymi.
adg
1
@adg ma rację: bigdecimal jest również złym wyborem dla waluty.
Eric Duminil,
1
@adg masz rację. W ciągu ostatnich kilku lat pracowałem z niektórymi aplikacjami księgowymi i finansowymi i przechowujemy wszystkie pola walutowe w liczbach całkowitych. W takich przypadkach jest znacznie bezpieczniej.
Guilherme Lages Santos
19

W Railsach 3.2.18: liczba dziesiętna zmienia się w: liczba całkowita podczas używania SQLServer, ale działa dobrze w SQLite. Przejście na: float rozwiązało dla nas ten problem.

Wyciągnięta lekcja brzmi: „zawsze używaj jednorodnych baz danych dotyczących programowania i wdrażania!”

ryan0
źródło
3
Dobra uwaga, 3 lata później robienia Railsów, całym sercem się zgadzam.
Jonathan Allard
3
„zawsze używaj jednorodnych baz danych dotyczących programowania i wdrażania!”
zx1986
15

W Rails 4.1.0 napotkałem problem z zapisywaniem szerokości i długości geograficznej w bazie danych MySql. Nie można zapisać dużej liczby ułamkowej przy typie danych zmiennoprzecinkowych. I zmieniam typ danych na dziesiętny i działam dla mnie.

  zmiana def
    zmiana_kolumna: miasta,: szerokość geograficzna,: dziesiętny,: precyzja => 15,: skala => 13
    change_column: miasta,: długość geograficzna,: dziesiętny,: precyzja => 15,: skala => 13
  koniec
Rokibul Hasan
źródło
Zapisuję moje: szerokość i długość geograficzną, ponieważ unosi się w Postgres, i to działa dobrze.
Scott W
3
@Robikul: tak, to dobrze, ale przesada. decimal(13,9) jest wystarczający dla szerokości i długości geograficznej. @ScottW: Nie przypominam sobie, ale jeśli Postgres używa pływaków IEEE, „działa dobrze”, ponieważ nie napotkałeś problemów… JESZCZE. Jest to niewystarczający format dla szerokości i długości geograficznej. W końcu będziesz mieć błędy w najmniej znaczących cyfrach.
Lonny Eachus
@LonnyEachus, co powoduje, że IEEE unosi się niewystarczająco dla lat / longs?
Alexander Suraphel
3
@AlexanderSuraphel Jeśli używasz dziesiętnej szerokości i długości geograficznej, zmiennoprzecinkowe IEEE jest podatne na błędy w najmniej znaczących cyfrach. Tak więc szerokość i długość geograficzna mogą mieć na przykład dokładność 1 metra, ale możesz mieć błędy 100 metrów lub więcej. Jest to szczególnie prawdziwe, jeśli używasz ich w obliczeniach.
Lonny Eachus,