Ruby BigDecimal sanity check (floating point newb))

est-ce que mon interprétation est correcte qu'avec Ruby BigDecimal types (même avec une précision et des longueurs d'échelle variables) devrait calculer avec précision ou dois-je anticiper shenanigans point flottant?

toutes mes valeurs dans une application de Rails sont de type BigDecimal et je vois des erreurs (ils ont des longueurs décimales différentes), en espérant que ce soit juste mes méthodes et pas mes types d'objet.

19
demandé sur Arslan Ali 2010-06-14 22:04:29

1 réponses

il y a deux écueils courants lorsqu'on travaille avec l'arithmétique à virgule flottante.

le premier problème est que les pointes flottantes rubis ont une précision fixe. En pratique ce sera soit 1) aucun problème pour vous ou 2) désastreux, ou 3) quelque chose entre les deux. Considérons ce qui suit:

# float
1.0e+25 - 9999999999999999900000000.0
#=> 0.0

# bigdecimal
BigDecimal("1.0e+25") - BigDecimal("9999999999999999900000000.0")
#=> 100000000

une différence de précision de 100 millions! Très sérieux, non?

excepté la précision erreur est seulement d'environ 0.0000000000001% du nombre original. C'est vraiment à vous de décider si c'est un problème ou pas. Mais le problème est éliminé en utilisant BigDecimal parce qu'il a une précision arbitraire. Votre seule limite est la mémoire disponible pour Ruby.

le deuxième problème est que les points flottants ne peuvent pas exprimer toutes les fractions avec précision. En particulier, ils ont des problèmes avec décimal fractions, parce que flotte en Ruby (et la plupart des autres langues) sont binaire points flottants. Par exemple, la fraction décimale 0.2 est une fraction binaire à répétition éternelle ( 0.001100110011... ). Cela ne peut jamais être stocké avec précision dans un point flottant binaire, quelle que soit la précision.

cela peut faire une grande différence lorsque vous arrondissez les chiffres. Considérons:

# float
(0.29 * 50).round
#=> 14  # not correct

# bigdecimal
(BigDecimal("0.29") * 50).round
#=> 15  # correct

Un BigDecimal peut décrire décimal fractions précisément. Cependant, il existe des fractions qui ne peuvent pas non plus être décrites avec précision avec une fraction décimale. Par exemple 1/9 est une fraction décimale à répétition éternelle ( 0.1111111111111... ).

encore une fois, cela vous mordra quand vous arrondir un nombre. Considérons:

# bigdecimal
(BigDecimal("1") / 9 * 9 / 2).round
#=> 0  # not correct

dans ce cas, en utilisant des points décimaux flottants donnera encore une erreur d'arrondissement.

Some conclusions:

  • flotteurs décimaux sont impressionnants si vous faites des calculs avec des fractions décimales (l'argent, par exemple).
  • Ruby's BigDecimal fonctionne aussi bien si vous avez besoin de points flottants de précision arbitraire, et ne se soucient pas vraiment si elles sont décimales ou binaires points flottants.
  • si vous travaillez avec des données (scientifiques), vous avez généralement affaire à des nombres de précision fixes; les flotteurs intégrés de Ruby seront probablement suffisants.
  • vous ne pouvez jamais vous attendre arithmétique avec n'importe quel type de point flottant pour être précis dans toutes les situations.
32
répondu molf 2010-06-14 21:47:39