1235 votes

Pourquoi ne pas utiliser Double ou Float pour représenter la monnaie ?

On m'a toujours dit de ne JAMAIS faire cela, et cette fois je vous pose la question : pourquoi ? Je suis sûr qu'il y a une très bonne raison, mais je ne la connais pas. :-P

1280voto

zneak Points 45458

Parce que les flottants et les doubles ne peuvent pas représenter avec précision les multiples en base 10 que nous utilisons pour l'argent. Ce problème ne concerne pas uniquement Java, mais tous les langages de programmation qui utilisent des types natifs à virgule flottante, car il découle de la manière dont les ordinateurs traitent les nombres à virgule flottante par défaut.

C'est ainsi qu'un IEEE-754 Un nombre à virgule flottante fonctionne : il dédie un bit au signe, quelques bits pour stocker un exposant pour la base, et le reste pour un multiple de cette base élevée. Cela conduit à ce que des nombres comme 10,25 soient représentés sous une forme similaire à celle qui suit 1025 * 10^-2 sauf qu'au lieu que la base soit 10, pour float et double s, c'est deux (donc ce serait 164 * 2^-4 ).

Même en base 10, cette notation ne peut pas représenter avec précision la plupart des fractions simples. Par exemple, vous ne pouvez pas représenter 1/3 comme un multiple d'une puissance de 10 : il vous faudrait stocker une quantité infinie de 3 et un exposant négatif infiniment grand, ce qui est tout simplement impossible. Cependant, pour les besoins de l'argent, dans la plupart des scénarios, tout ce dont vous avez besoin est de pouvoir stocker des multiples de 10. -2 donc ce n'est pas si mal.

De même que certaines fractions ne peuvent pas être représentées exactement comme des multiples d'une puissance de dix, certaines d'entre elles ne peuvent pas non plus être représentées exactement comme des multiples d'une puissance de deux. En fait, les seules fractions de cent entre 0/100 et 100/100 (qui sont significatives lorsqu'il s'agit d'argent car ce sont des centimes entiers) qui peuvent être représentées exactement comme un nombre binaire à virgule flottante IEEE-754 sont 0, 0,25, 0,5, 0,75 et 1. Toutes les autres sont faussées par une petite quantité.

Représenter l'argent comme un double ou float aura probablement l'air bon au début, car le logiciel arrondit les petites erreurs, mais au fur et à mesure que vous effectuez des additions, des soustractions, des multiplications et des divisions sur des nombres inexacts, vous perdrez de plus en plus de précision, car les erreurs s'accumulent. Les flottants et les doubles sont donc inadaptés au traitement de l'argent, où une précision parfaite pour les multiples des puissances en base 10 est requise.

Une solution qui fonctionne dans presque toutes les langues consiste à utiliser des nombres entiers à la place et à compter les centimes. Par exemple, 1025 correspondrait à 10,25 $. Plusieurs langages ont également des types intégrés pour traiter l'argent. Entre autres, Java dispose du type BigDecimal et le C# dispose de la classe decimal type.

366voto

dogbane Points 85749

De Bloch, J., Effective Java, 2nd ed, Item 48 :

Le site float et double types sont particulièrement mal adaptés aux calculs monétaires car il est impossible de représenter 0,1 (ou toute autre puissance négative de dix) comme une float ou double exactement.

Par exemple, supposons que vous ayez 1,03 $ et que vous dépensez 42c. Combien d'argent vous reste-t-il ?

System.out.println(1.03 - .42);

imprime 0.6100000000000001 .

La bonne façon de résoudre ce problème est d'utiliser BigDecimal , int ou long pour les calculs monétaires.

96voto

Randy D Oxentenko Points 277

Ce n'est ni une question d'exactitude, ni une question de précision. Il s'agit de répondre aux attentes des humains qui utilisent la base 10 pour les calculs au lieu de la base 2. Par exemple, l'utilisation de doubles pour les calculs financiers ne produit pas de réponses "fausses" dans un sens mathématique, mais elle peut produire des réponses qui ne correspondent pas aux attentes dans un sens financier.

Même si vous arrondissez vos résultats à la dernière minute avant la sortie, vous pouvez toujours obtenir occasionnellement un résultat en utilisant des doubles qui ne correspond pas aux attentes.

En utilisant une calculatrice, ou en calculant les résultats à la main, 1,40 * 165 = 231 exactement. Cependant, en utilisant des doubles en interne, sur mon compilateur / environnement système d'exploitation, il est stocké comme un nombre binaire proche de 230.99999... donc si vous tronquez le nombre, vous obtenez 230 au lieu de 231. Vous pouvez penser que l'arrondi au lieu de la troncature aurait donné le résultat souhaité de 231. C'est vrai, mais l'arrondi implique toujours une troncature. Quelle que soit la technique d'arrondi que vous utilisez, il existe toujours des conditions limites comme celle-ci qui arrondissent à la baisse alors que vous vous attendez à ce qu'elles arrondissent à la hausse. Elles sont suffisamment rares pour ne pas être découvertes par des tests ou des observations occasionnels. Vous devrez peut-être écrire du code pour rechercher des exemples illustrant des résultats qui ne se comportent pas comme prévu.

Supposons que vous vouliez arrondir quelque chose au centime le plus proche. Vous prenez donc votre résultat final, le multipliez par 100, ajoutez 0,5, tronquez, puis divisez le résultat par 100 pour revenir aux centimes. Si le nombre interne que vous avez stocké était 3,46499999.... au lieu de 3,465, vous allez obtenir 3,46 au lieu de 3,47 lorsque vous arrondissez le nombre au centime le plus proche. Mais vos calculs en base 10 ont peut-être indiqué que la réponse devait être 3,465 exactement, ce qui devrait clairement être arrondi à 3,47 et non à 3,46. Ce genre de choses se produit occasionnellement dans la vie réelle lorsque vous utilisez des doubles pour des calculs financiers. C'est rare, et le problème passe souvent inaperçu, mais cela arrive.

Si vous utilisez la base 10 pour vos calculs internes au lieu des doubles, les réponses sont toujours exactement ce qui est attendu par les humains, en supposant qu'il n'y ait pas d'autres bugs dans votre code.

70voto

Rob Scala Points 67

Je suis troublé par certaines de ces réponses. Je pense que les doubles et les flottants ont leur place dans les calculs financiers. Certes, lors de l'addition et de la soustraction de montants monétaires non fractionnaires, il n'y aura pas de perte de précision en utilisant les classes integer ou BigDecimal. Mais lorsque vous effectuez des opérations plus complexes, vous obtenez souvent des résultats qui dépassent plusieurs ou plusieurs décimales, quelle que soit la manière dont vous stockez les chiffres. Le problème est de savoir comment présenter le résultat.

Si votre résultat est à la limite entre l'arrondi vers le haut et l'arrondi vers le bas, et que le dernier centime compte vraiment, vous devriez probablement indiquer au spectateur que la réponse se situe presque au milieu - en affichant davantage de décimales.

Le problème avec les doubles, et plus encore avec les flottants, est qu'ils sont utilisés pour combiner de grands nombres et de petits nombres. En java,

System.out.println(1000000.0f + 1.2f - 1000000.0f);

résulte en

1.1875

41voto

Nathan Hughes Points 30377

Les flottants et les doubles sont approximatifs. Si vous créez un BigDecimal et que vous passez un flottant dans le constructeur, vous verrez ce que le flottant représente réellement :

groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375

ce n'est probablement pas ainsi que vous voulez représenter 1,01 $.

Le problème est que la spécification IEEE ne permet pas de représenter exactement toutes les fractions, certaines d'entre elles finissent par être des fractions répétées, ce qui entraîne des erreurs d'approximation. Étant donné que les comptables aiment que les résultats soient exacts au centime près et que les clients seront mécontents s'ils paient leur facture et qu'après le traitement du paiement, ils doivent 0,01 et se voient facturer des frais ou ne peuvent pas clôturer leur compte, il est préférable d'utiliser des types exacts comme decimal (en C#) ou java.math.BigDecimal en Java.

Ce n'est pas que l'erreur n'est pas contrôlable si vous arrondissez : voir cet article de Peter Lawrey . Il est tout simplement plus facile de ne pas avoir à arrondir en premier lieu. La plupart des applications qui manipulent de l'argent ne font pas appel à beaucoup de mathématiques, les opérations consistent à ajouter des choses ou à allouer des montants à différents compartiments. L'introduction de la virgule flottante et des arrondis ne fait que compliquer les choses.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X