Question

Je suis en train d'écrire un code qui traitera des devises, les frais, etc. Je vais utiliser la classe BigDecimal pour les mathématiques et le stockage, mais nous avons rencontré quelque chose de bizarre avec elle.

Cette déclaration:

1876.8 == BigDecimal('1876.8')

retourne false.

Si je lance ces valeurs par une chaîne de mise en forme "%.13f" je reçois:

"%.20f" % 1876.8 => 1876.8000000000000
"%.20f" % BigDecimal('1876.8') => 1876.8000000000002

Notez le 2 supplémentaire de la BigDecimal à la dernière décimale.

Je pensais que BigDecimal était censé contrer les erreurs de stockage des nombres réels directement dans le flottant natif de l'ordinateur. Où est-ce 2 vient?

Était-ce utile?

La solution

Vous avez raison, BigDecimal doit être le ranger correctement, ma meilleure estimation est:

  • BigDecimal stocke correctement la valeur
  • Lorsque passée à une fonction mise en forme de chaîne, BigDecimal est jeté comme une valeur en virgule flottante précision inférieure, ce qui crée le ... 02.
  • Lorsque comparé directement avec un flotteur, le flotteur a une place décimale supplémentaire bien au-delà du 20 vous voyez (flotteurs classiques ne peuvent pas être comparés behavoir).

De toute façon, il est peu probable d'obtenir des résultats précis comparant un flotteur à un BigDecimal.

Autres conseils

Il ne vous donnera pas autant de contrôle sur le nombre de décimales, mais le mécanisme de format classique pour BigDecimal semble être:

a.to_s('F')

Si vous avez besoin d'un plus grand contrôle, pensez à utiliser le joyau de l'argent, en supposant que votre problème de domaine est la plupart du temps sur la monnaie.

gem install money

Ne pas comparer les fractions de chaîne décimale FPU pour l'égalité

Le problème est que la comparaison de l'égalité d'une valeur flottante ou double avec une constante décimale qui contient une fraction est rarement couronnée de succès.

Très peu de fractions décimales ont des valeurs chaîne exactes dans la représentation binaire FP, de sorte que les comparaisons d'égalité sont généralement condamnés. *

Pour répondre à votre question précise, le 2 provient d'une conversion légèrement différente de la fraction de chaîne décimale dans le format Float. Parce que la fraction ne peut pas être représenté exactement, il est possible que deux calculs tiendront compte des quantités différentes de précision dans les calculs intermédiaires et, finalement, finissent par arrondir le résultat à un IEEE 754 double précision mantisse différemment 52 bits. Il importe peu parce que est pas de représentation exacte de toute façon, mais on est sans doute plus de mal que l'autre.

En particulier, le 1876.8 ne peut pas être représenté exactement par un objet FP, en fait, entre 0,01 et 0,99, seulement 0,25, 0,50, 0,75 et avoir des représentations binaires exactes. Tous les autres, comprennent 1876,8, répéter toujours et sont arrondis à 52 bits. Cela représente environ la moitié de la raison pour laquelle BigDecimal existe. (L'autre moitié de la raison est la précision des données fixe FP:. Parfois, vous avez besoin de plus)

Ainsi, le résultat que vous obtenez lorsque l'on compare une valeur réelle de la machine avec une chaîne décimale dépend de chaque bit constante dans la fraction binaire ... jusqu'à 1/2 52 ... et même il faut alors l'arrondissement.

S'il y a quelque chose le moindrement (hehe, bits, désolé) imparfait du processus qui a produit le numéro ou le code de conversion d'entrée, ou toute autre chose en cause, ils ne regarderont pas exactement égale.

Un argument pourrait même être que la comparaison devrait échouent toujours parce qu'aucun FPU IEEE format peut même représenter exactement ce nombre. Ils ne sont vraiment pas égaux, même si elles ressemblent à cela. Sur la gauche, votre chaîne décimale a été convertie en une chaîne binaire, et la plupart des chiffres ne convertit pas exactement. A droite, il est toujours une chaîne décimale.

Alors, ne pas mélanger les flotteurs avec BigDecimal, il suffit de comparer un BigDecimal avec un autre BigDecimal. (Même lorsque les deux opérandes sont flotteurs, les essais pour l'égalité exige beaucoup de soins ou un test flou Aussi, ne pas faire confiance à tous les chiffres au format:. La mise en forme de sortie transportera reliquats ainsi du côté droit de la fraction, de sorte que vous ne commencez pas généralement voir des zéros, vous verrez simplement les valeurs de déchets.)


* Le problème: le nombre de machines sont x / 2 n , mais constantes décimales sont x / (2 n * 5 m ). Votre valeur signe, exposant et mantisse est le 0 10000001001 1101010100110011001100110011001100110011001100110011... infiniment répéter Ironie du sort, l'arithmétique FP est parfaitement précis et des comparaisons d'égalité fonctionnent parfaitement bien quand la valeur n'a pas fraction.

David dit BigDecimal stocke à droite

 p (BigDecimal('1876.8') * 100000000000000).to_i

retourne 187680000000000000

, oui, la mise en forme de chaîne est ruinent

Si vous n'avez pas besoin cents fractionnaires, envisager de stocker et de manipuler la monnaie comme un entier, puis en divisant par 100 quand il est temps d'afficher. Je trouve cela plus facile que de traiter les problèmes de précision inévitables de stockage et de manipulation en virgule flottante.

Sous Mac OS X, je suis en cours d'exécution ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9]

irb(main):004:0> 1876.8 == BigDecimal('1876.8') => true

Cependant, étant Ruby, je pense que vous devriez penser en termes de messages envoyés à des objets. Qu'est-ce que ce retour à vous:

BigDecimal('1876.8') == 1876.8

Les deux ne sont pas équivalents, et si vous essayez d'utiliser la capacité de BigDecimal de déterminer avec précision l'égalité décimale, il devrait être le récepteur du message poser des questions sur l'égalité.

Pour la même raison, je ne pense pas que le formatage du BigDecimal en envoyant un message de format à la chaîne de format est la bonne approche soit.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top