Float vs Decimal dans ActiveRecord

283

Parfois, les types de données Activerecord me confondent. Euh, souvent. L'une de mes questions éternelles est, pour un cas donné,

Dois-je utiliser :decimalou :float?

J'ai souvent rencontré ce lien, ActiveRecord:: decimal vs: float? , mais les réponses ne sont pas assez claires pour que je sois certain:

J'ai vu de nombreux threads où les gens recommandent de ne pas utiliser float et d'utiliser toujours décimal. J'ai également vu des suggestions de certaines personnes d'utiliser le flotteur uniquement pour des applications scientifiques.

Voici quelques exemples:

  • Géolocalisation / latitude / longitude: -45.756688, 120.5777777, ...
  • Ratio / pourcentage: 0.9, 1.25, 1.333, 1.4143, ...

J'ai utilisé :decimaldans le passé, mais j'ai trouvé que traiter des BigDecimalobjets dans Ruby était inutilement gênant par rapport à un flotteur. Je sais également que je peux utiliser :integerpour représenter l'argent / cents, par exemple, mais cela ne convient pas tout à fait à d'autres cas, par exemple lorsque des quantités dont la précision peut changer avec le temps.

  • Quels sont les avantages / inconvénients de l'utilisation de chacun?
  • Quelles seraient les bonnes règles de base pour savoir quel type utiliser?
Jonathan Allard
la source

Réponses:

427

Je me souviens que mon professeur CompSci avait dit de ne jamais utiliser de flotteurs comme monnaie.

La raison en est que la spécification IEEE définit les flottants au format binaire. Fondamentalement, il stocke le signe, la fraction et l'exposant pour représenter un flotteur. C'est comme une notation scientifique pour le binaire (quelque chose comme +1.43*10^2). Pour cette raison, il est impossible de stocker exactement des fractions et des décimales dans Float.

C'est pourquoi il existe un format décimal. Si tu fais ça:

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

alors que si vous le faites

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

Donc, si vous avez affaire à de petites fractions, comme des intérêts composés, ou peut-être même à la géolocalisation, je recommande fortement le format décimal, car au format décimal 1.0/10est exactement 0,1.

Cependant, il convient de noter qu'en dépit d'être moins précis, les flottants sont traités plus rapidement. Voici une référence:

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

Répondre

Utilisez float lorsque vous ne vous souciez pas trop de la précision. Par exemple, certaines simulations et calculs scientifiques n'ont besoin que de 3 ou 4 chiffres significatifs. Ceci est utile pour échanger la précision contre la vitesse. Comme ils n'ont pas autant besoin de précision que de vitesse, ils utiliseraient float.

Utilisez décimal si vous avez affaire à des nombres qui doivent être précis et résumer à un nombre correct (comme les intérêts composés et les choses liées à l'argent). N'oubliez pas: si vous avez besoin de précision, vous devez toujours utiliser la décimale.

Iuri G.
la source
Donc, si je comprends bien, float est en base-2 alors que décimal est en base-10? Quelle serait une bonne utilisation du flotteur? Que fait et démontre votre exemple?
Jonathan Allard du
1
Tu ne veux pas dire +1.43*2^10plutôt que +1.43*10^2?
Cameron Martin
47
Pour les futurs visiteurs, le meilleur type de données pour la devise est un entier, pas un nombre décimal. Si la précision du champ est de quelques centimes, alors le champ serait un entier en centimes (pas une décimale en dollars). J'ai travaillé dans le service informatique d'une banque et c'est ainsi que cela s'est fait. Certains champs étaient plus précis (comme les centièmes de cent), mais ils étaient toujours des entiers.
ADG
1
@adg a raison: bigdecimal est également un mauvais choix pour la devise.
Eric Duminil
1
@adg tu as raison. J'ai travaillé avec certaines applications comptables et financières au cours des dernières années et nous stockons tous nos champs de devises dans des colonnes entières. C'est beaucoup plus sûr pour ces cas.
Guilherme Lages Santos
19

Dans Rails 3.2.18,: décimal se transforme en: entier lors de l'utilisation de SQLServer, mais cela fonctionne très bien dans SQLite. Passer à: float a résolu ce problème pour nous.

La leçon apprise est "utilisez toujours des bases de données de développement et de déploiement homogènes!"

ryan0
la source
3
Bon point, 3 ans plus tard de faire Rails, je suis entièrement d'accord.
Jonathan Allard
3
"utilisez toujours des bases de données de développement et de déploiement homogènes!"
zx1986
15

Dans Rails 4.1.0, j'ai rencontré un problème avec l'enregistrement de la latitude et de la longitude dans la base de données MySql. Il ne peut pas enregistrer un grand nombre de fractions avec un type de données flottant. Et je change le type de données en décimal et travaille pour moi.

  changement de def
    change_column: villes,: latitude,: décimal,: précision => 15,: échelle => 13
    change_column: villes,: longitude,: décimal,: précision => 15,: échelle => 13
  fin
Rokibul Hasan
la source
Je sauvegarde mes: latitude et: longitude sous forme de flotteurs dans Postgres, et cela fonctionne très bien.
Scott W
3
@Robikul: oui, c'est bien, mais exagéré. decimal(13,9) est suffisant pour la latitude et la longitude. @ScottW: Je ne me souviens pas, mais si Postgres utilise des flottants IEEE, cela "fonctionne très bien" car vous n'avez pas rencontré de problèmes ... ENCORE. C'est un format insuffisant pour la latitude et la longitude. Yo finira par avoir des erreurs dans les chiffres les moins significatifs.
Lonny Eachus
@LonnyEachus qu'est-ce qui rend les flotteurs IEEE insuffisants pour les lat / longs?
Alexander Suraphel
3
@AlexanderSuraphel Si vous utilisez la latitude et la longitude décimales, un flottant IEEE est susceptible d'erreurs dans les chiffres les moins significatifs. Ainsi, votre latitude et longitude peuvent avoir une précision de 1 mètre par exemple, mais vous pouvez avoir des erreurs de 100 mètres ou plus. Cela est particulièrement vrai si vous les utilisez dans les calculs.
Lonny Eachus