Pourquoi ne pas utiliser le Double ou Float pour représenter la monnaie?
J'ai toujours dit jamais pour représenter de l'argent avec double
ou float
types, et cette fois, je pose la question pour vous: pourquoi?
Je suis sûr qu'il ya une très bonne raison, je ne sais tout simplement pas ce que c'est.
- Voir ce DONC, la question: les Erreurs d'Arrondi?
- Juste pour être clair, il ne devrait pas être utilisé pour quelque chose qui exige de la précision-et pas seulement de la monnaie.
- Ils ne devraient pas être utilisés pour tout ce qui requiert de la précision. Mais le double de 53 bits de poids faible (~16 chiffres décimaux) sont généralement assez bon pour des choses qui ne nécessitent que l'exactitude.
- Votre commentaire complètement induit en erreur binaire en virgule flottante est bon et de ce qu'il n'est pas bon pour. Lire la réponse par zneak ci-dessous, et s'il vous plaît supprimer votre trompeuse commentaire.
Vous devez vous connecter pour publier un commentaire.
Parce que les chars et les doubles ne peuvent pas représenter fidèlement la base de 10 multiples que nous utilisons pour l'argent. Ce problème n'est pas seulement pour Java, c'est pour tout langage de programmation qui utilise la base de 2 les types à virgule flottante.
En base 10, vous pouvez écrire à 10,25 comme 1025 * 10-2 (un nombre entier de fois une puissance de 10). La norme IEEE-754 nombres à virgule flottante sont différents, mais un moyen très simple de penser à eux est de multiplier par une puissance de deux au lieu. Par exemple, vous pourriez être à la recherche à 164 * 2-4 (un nombre entier de fois une puissance de deux), qui est aussi égal à 10,25. Ce n'est pas la façon dont les nombres sont représentés en mémoire, mais les maths conséquences sont les mêmes.
Même en base 10, cette notation ne peut pas représenter fidèlement la plupart des fractions simples. Par exemple, vous ne pouvez pas représenter 1/3: la représentation décimale est de répéter (0.3333...), donc il n'y a pas finie d'entiers que vous pouvez multiplier par une puissance de 10 pour obtenir 1/3. Vous pourriez s'installer sur une longue séquence de 3 et un petit exposant, comme 333333333 * 10-10, mais elle n'est pas exacte: si vous multipliez-le par 3, vous n'aurez pas 1.
Cependant, dans le but de compter l'argent, au moins pour les pays dont l'argent est évalué dans un ordre de grandeur du dollar AMÉRICAIN, généralement tout ce que vous avez besoin est d'être capable de stocker de multiples de 10-2, donc il n'a pas vraiment d'importance que 1/3 ne peuvent pas être représentés.
Le problème avec des flotteurs et des doubles, c'est que le grande majorité de l'argent-comme les nombres n'ont pas une représentation exacte comme un nombre entier de fois une puissance de 2. En fait, seuls les multiples de 0,01 entre 0 et 1 (qui sont importantes lorsque vous traitez avec de l'argent parce qu'ils sont entier cents) qui peut être représenté exactement comme la norme IEEE-754 binaire nombre à virgule flottante sont 0, 0.25, 0.5, 0.75 et 1. Tous les autres sont hors par une petite quantité. Comme une analogie à l'0.333333 exemple, si vous prenez la valeur à virgule flottante pour 0,1 et vous le multipliez par 10, vous n'aurez pas 1.
Représentant de l'argent comme un
double
oufloat
aura probablement l'air bon tout d'abord que le logiciel de tours au large de la petite erreurs, mais que vous effectuez plus d'additions, des soustractions, des multiplications et des divisions sur l'imprécision des chiffres, les erreurs s'accumulent et vous vous retrouverez avec des valeurs qui sont visiblement pas exacte. Cela fait des flotteurs et des doubles insuffisante pour faire face à l'argent, d'où une parfaite précision des multiples de la base 10, des pouvoirs nécessaires.Une solution qui fonctionne dans n'importe quelle langue est d'utiliser des entiers au lieu de cela, et de compter les centimes. Par exemple, 1025 serait de 10,25$. Plusieurs langues ont aussi des types intégrés à traiter avec de l'argent. Entre autres, Java a la
BigDecimal
classe, et C# a ladécimal
type.1.0 / 10 * 10
ne peut pas être le même que 1.0.float
s etdouble
s sont utiles lorsque vous avez besoin de 5 décimales? Si oui, s'il vous plaît laissez-moi vous présenter lesBigDecimal
classe en Java et ledecimal
type en C#, qui visent à résoudre le problème exactement.double
a une précision d'environ 15 chiffres après la virgule, donc, par souci de pragmatisme, je considère que l'avertissement qu'il pourrait ressembler bien au premier abord est important.BigDecimal
vraiment utile ? Il n'a pas une précision infinie, il n'offre plus de contrôle sur l'arrondissement de comportement. Droit ?0.1 + 0.2 != 0.3
.0b0.00011001100110011001100110011001100110011001100110011010
et 100b1010
. Si vous multipliez ces deux nombres binaires, vous obtenez1.0000000000000000000000000000000000000000000000000000010
, et après qui a été arrondi à la disposition des 53 chiffres binaires, vous avez exactement 1. Le problème avec les chars n'est pas qu'ils toujours aller mal, mais qu'ils parfois faire - comme avec l'exemple de 0.1 + 0.2 ≠ 0.3.Long
est préférable par rapport àBigDecimal
puisque (i) les transactions ne peuvent pas avoir les fractions de cent (/pence/etc) (ii)BigDecimal
est cher w.r.t. CPU, de la RAM et de stockage. (iii) de Nombreux formats de sérialisation ne prennent pas en chargeBigDecimal
, cela peut causer de petites erreurs (par exemple, JSON) (iv)BigDecimal
n'est pas un type primitif. (v) Chaque langue traiteDecimal
différemment, par exemple en Java, on doit faire attention à utiliser le bon BigDecimal constructeur (vi) Certains systèmes "silencieusement tronquer" non arbitraire de la précision des décimales, par exemple MySQL.De Bloch, J., Efficace Java, 2nd ed, Point 48:
Si
BigDecimal
a quelques mises en garde (consultez actuellement accepté de répondre).long a = 104
et compter de centimes au lieu de dollars.BigDecimal
.double
et arrondissement avant chaque sortie ou la comparaison fonctionne probablement le meilleur (l'erreur d'arrondi peuvent incroyablement influence certains endroits dans tout calcul réaliste).BigDecimal
.Ce n'est pas une question de précision, il n'est ni une question de précision. C'est une question de répondre aux attentes des humains qui utilisent la base 10 pour les calculs au lieu de la base 2. Par exemple, en utilisant un double pour les calculs financiers ne permet pas de produire des réponses qui sont "mauvais" dans un sens mathématique, mais il peut produire des réponses qui ne sont pas ce qui est attendu dans une logique financière.
Même si vous arrondir vos résultats à la dernière minute avant la sortie, vous pouvez parfois obtenir un résultat en utilisant un double qui ne correspondent pas aux attentes.
À l'aide d'une calculatrice, ou le calcul des résultats par la main, 1.40 * 165 = 231 exactement. Cependant, en interne, à l'aide de doubles, sur mon compilateur /environnement de système d'exploitation, il est stocké sous forme d'un nombre binaire à proximité de 230.99999... donc si vous tronquez le nombre, vous obtenez 230 au lieu de 231. Vous pouvez raison que l'arrondi au lieu de les tronquer aurait donné le résultat souhaité de 231. C'est vrai, mais l'arrondi implique toujours la troncature. Quelle que soit la technique d'arrondissement que vous utilisez, il y a encore des conditions aux limites comme celui-ci qui va s'arrondir vers le bas lorsque vous attendez pour arrondir. Ils sont assez rare que, souvent, ils ne seront pas trouvés au hasard des essais ou de l'observation. Vous pourriez avoir à écrire du code pour rechercher des exemples qui illustrent les résultats qui ne se comportent pas comme prévu.
Suppose que vous voulez quelque chose rondes au centime le plus proche. Vous prenez donc votre résultat final, multipliez par 100, ajouter 0,5, tronquer, puis à diviser le résultat par 100 pour obtenir le retour à la monnaie. Si le numéro que vous avez stocké a été 3.46499999.... au lieu de 3.465, vous allez obtenir de 3,46 au lieu 3.47 lorsque vous arrondissez le nombre au centime le plus proche. Mais votre base 10 des calculs ont indiqué que la réponse devrait être 3.465 exactement, ce qui devrait à l'évidence ronde jusqu'à 3,47, pas vers le bas à 3.46. Ces sortes de choses se produisent parfois dans la vie réelle lorsque vous utilisez doubles pour les calculs financiers. Il est rare, de sorte qu'il passe souvent inaperçu comme un problème, mais ça arrive.
Si vous utilisez la base de 10 pour vos calculs internes au lieu de double, les réponses sont toujours exactement ce qui est prévu par les humains, en supposant qu'aucun autre des bugs dans votre code.
Math.round(0.49999999999999994)
retour 1?Je suis troublé par certaines de ces réponses. Je pense que les doubles et les chars ont une place dans les calculs financiers. Certes, lors de l'ajout et la soustraction des non-fractionnée montants monétaires, il n'y aura pas de perte de précision lors de l'utilisation de entier des classes ou des BigDecimal classes. Mais au moment d'effectuer des opérations plus complexes, vous finissent souvent avec des résultats qui sortent plusieurs ou de nombreuses décimales, peu importe la façon dont vous stockez les numéros. La question est de savoir comment vous présenter le résultat.
Si votre résultat est à la frontière entre le fait d'être arrondis et arrondis à la baisse, et que la dernière pièce d'un cent est vraiment important, vous devriez probablement dire que le spectateur que la réponse est presque dans le moyen - par l'affichage de plus de décimales.
Le problème avec les doubles, et de plus avec des chars, c'est quand ils sont utilisés pour combiner un grand nombre et, en petit nombre. En java,
résultats dans
Chars et les doubles sont approximatives. Si vous créez un BigDecimal et passer un flotteur dans le constructeur vous de voir ce que le flotteur en fait égal à:
ce n'est probablement pas la façon dont vous voulez représenter $1.01.
Le problème est que la spécification IEEE n'ont pas un moyen de représenter avec exactitude toutes les fractions, certains d'entre eux finissent par se répéter fractions si vous vous retrouvez avec le rapprochement des erreurs. Depuis les comptables comme des choses à venir exactement à la pièce de un cent, et les clients seront ennuyé si ils payer leur facture et après le paiement est traité qu'ils doivent .01 et ils paient une taxe ou ne peuvent pas fermer leur compte, il est préférable d'utiliser les types exacts comme en décimal (en C# ou java.les mathématiques.BigDecimal en Java.
Ce n'est pas que l'erreur n'est pas contrôlable si vous tour: voir cet article de Peter Lawrey. C'est juste plus facile de ne pas avoir à en rond dans la première place. La plupart des applications qui manipulent l'argent ne faites pas appel à beaucoup de maths, les opérations consistent à ajouter des choses ou de l'allocation de montants à compartiments différents. L'introduction de nombres à virgule flottante et arrondi juste complique les choses.
float
,double
etBigDecimal
sont représentées exact valeurs. Code pour objet de conversion sont inexactes ainsi que d'autres opérations. Les types eux-mêmes ne sont pas inexact.Je vais le risque d'être downvoted, mais je pense que l'inadaptation de nombres en virgule flottante pour les calculs de devise est surfait. Tant que vous assurez-vous de faire les cent-arrondi correctement et avoir assez de chiffres significatifs afin de contrer le binaire-décimal représentation incompatibilité expliqué par zneak, il n'y aura pas de problème.
Gens de calcul avec la monnaie dans Excel ont toujours utilisé la double précision, de flotteurs (il n'y a pas de type de devise dans Excel) et je n'ai pas encore vu quelqu'un se plaindre sur les erreurs d'arrondi.
Bien sûr, vous devez rester à l'intérieur de la raison; par exemple, une simple boutique en ligne, n'auraient probablement pas l'expérience de tout problème avec la double précision, flotteurs, mais si vous n'avez par exemple la comptabilité ou l'autre tout ce qui nécessite l'ajout d'un grand (sans restriction), quantité de numéros, vous ne voulez pas toucher les nombres à virgule flottante avec une perche de dix pieds.
Même s'il est vrai que le type à virgule flottante peut représenter qu'environ décimal de données, il est également vrai que si l'on arrondit les nombres à la précision nécessaire avant de les présenter, on obtient le résultat correct. Généralement.
Généralement parce que le double de type a une précision de moins de 16 chiffres. Si vous avez besoin de plus de précision ce n'est pas un type approprié. Aussi des approximations peuvent s'accumuler.
Il faut dire que même si vous utilisez le point fixe de l'arithmétique, vous avez encore de chiffres ronds, ce n'était pas pour le fait que BigInteger et BigDecimal des erreurs si vous obtenir périodiquement des nombres décimaux. Il y a donc une approximation aussi ici.
Par exemple COBOL, historiquement utilisé pour les calculs financiers, a une précision maximum de 18 chiffres. Donc, il est souvent implicite de l'arrondissement.
De conclure, à mon avis le double est impropre principalement pour ses 16 chiffres significatifs, ce qui peut être insuffisant, non pas parce qu'elle est approximative.
Envisager la suite de la sortie de l'ultérieures du programme. Il montre que, après arrondi double donne le même résultat que BigDecimal jusqu'à une précision de 16.
Le résultat de nombre à virgule flottante n'est pas exact, ce qui les rend impropres à tout calcul financier qui exige résultat exact et pas l'approximation. float et double sont conçus pour l'ingénierie et de calcul scientifique et de nombreuses fois, ne produit pas de résultat exact que le résultat à virgule flottante de calcul peut varier d'une JVM à la JVM. Regardez ci-dessous un exemple de BigDecimal et double primitive qui est utilisé pour représenter la valeur de l'argent, son assez clair que la virgule flottante calcul peut ne pas être exacte et l'on devrait utiliser BigDecimal pour les calculs financiers.
De sortie:
Comme dit plus tôt ", le Représentant d'argent que d'un lit double ou float aura probablement l'air bon tout d'abord que le logiciel de tours au large de la petite erreurs, mais que vous effectuez plus d'additions, des soustractions, des multiplications et des divisions sur l'imprécision des chiffres, vous allez perdre plus de et plus de précision que les erreurs s'accumulent. Cela fait des flotteurs et des doubles insuffisante pour faire face à l'argent, d'où une parfaite précision des multiples de la base 10 des pouvoirs est nécessaire."
Enfin Java a un moyen standard pour travailler avec la Monnaie Et de l'Argent!
JSR 354: l'Argent et la Monnaie de l'API
JSR 354 fournit une API pour la représentation, le transport, et en effectuant les calculs complets avec de l'Argent et de la Monnaie. Vous pouvez le télécharger à partir de ce lien:
JSR 354: l'Argent et la Monnaie de l'API Télécharger
La spécification se compose des éléments suivants:
Échantillon des Exemples de JSR 354: l'Argent et la Monnaie de l'API:
Un exemple de création d'un MonetaryAmount et l'impression sur la console ressemble à ceci:
Lors de l'utilisation de l'implémentation de référence de l'API, le code nécessaire est beaucoup plus simple:
L'API prend également en charge les calculs avec MonetaryAmounts:
CurrencyUnit et MonetaryAmount
MonetaryAmount a différentes méthodes qui permet d'accéder à l'assigné à la monnaie, du numérique le montant, sa précision et plus:
MonetaryAmounts peut être arrondi à l'aide d'un opérateur d'arrondi:
Lorsque l'on travaille avec des collections de MonetaryAmounts, quelques belles utilitaire de méthodes de filtrage, de tri et de regroupement sont disponibles.
Personnalisé MonetaryAmount opérations
Ressources:
La manipulation de l'argent et des devises en Java avec la JSR 354
La recherche dans la Java 9 de l'Argent et de la Monnaie de l'API (JSR 354)
Voir Aussi: JSR 354 - Monnaie et de l'Argent
Si votre calcul implique plusieurs étapes, en précision arbitraire ne couvre pas 100%.
Le seul moyen fiable pour une utilisation parfaite de la représentation des résultats(Utilisation d'une coutume Fraction de données de type de lot de la division des opérations de la dernière étape) et seulement convertir en notation décimale dans la dernière étape.
Précision arbitraire de ne pas aider, car il peut toujours être des nombres qui a autant de décimales, ou certains résultats tels que 0.6666666... Non arbitraire de la représentation couvrira le dernier exemple. Ainsi, vous aurez de petites erreurs à chaque étape.
Cette erreur va ajouter peut éventuellement devenir pas facile à ignorer plus. Ceci est appelé La Propagation Des Erreurs.
La plupart des réponses ont mis en évidence les raisons pour lesquelles on ne devrait pas utiliser de doubles pour l'argent et la monnaie calculs. Et je suis totalement d'accord avec eux.
Cela ne signifie pas cependant que les doubles ne peuvent jamais être utilisées à cette fin.
J'ai travaillé sur un certain nombre de projets avec de très faibles exigences du gc, et ayant BigDecimal objets a été un grand contributeur à cette surcharge.
C'est le manque de compréhension de la double représentation et le manque d'expérience dans la gestion de l'exactitude et de la précision qu'apporte cette sage suggestion.
Vous pouvez le faire fonctionner, si vous êtes capable de gérer la précision et l'exactitude des exigences de votre projet, ce qui doit être fait sur la base de ce que la gamme de valeurs doubles est une affaire.
Vous pouvez vous référer à la goyave est FuzzyCompare méthode pour obtenir plus d'idée. Le paramètre de tolérance est la clé.
Nous avons traité ce problème de négoce de titres d'application et nous avons fait une recherche exhaustive sur ce que les tolérances à utiliser pour les différentes valeurs numériques dans différentes gammes.
Aussi, il pourrait y avoir des situations où vous êtes tenté d'utiliser la Double wrappers comme une carte de clé de hachage avec carte du cours de la mise en œuvre. Il est très risqué parce que Double.d'égal à égal et le code de hachage par exemple, les valeurs "0.5" & "de 0,6 - de 0,1" va causer de gros dégâts.
Je préfère utiliser l'Entier ou Longues à représenter la monnaie. BigDecimal jonques le code source de trop.
Vous avez juste à savoir que tous vos valeurs en cents. Ou la valeur la plus basse, quelle que soit la monnaie que vous utilisez.
Beaucoup de réponses postées à cette question de discuter de l'IEEE et les normes entourant l'arithmétique à virgule flottante.
Venant d'un non-ordinateur de connaissances en sciences (physique et de l'ingénierie), j'ai tendance à voir les problèmes d'un point de vue différent. Pour moi, la raison pour laquelle je ne pas utiliser un lit double ou float dans un calcul mathématique est que je voudrais perdre trop d'information.
Quelles sont les alternatives? Il y a beaucoup (et bien d'autres dont je ne suis pas au courant!).
BigDecimal en Java native du langage Java.
Apfloat est une autre précision arbitraire de la bibliothèque Java.
Le type de données décimal en C# de Microsoft .FILET de rechange pour 28 chiffres significatifs.
SciPy (Scientifique Python) peut sans doute aussi gérer les calculs financiers (je n'ai pas essayé, mais je soupçonne donc).
GNU Multiple Precision de la Bibliothèque (BPF) et de la GNU MFPR de la Bibliothèque sont deux gratuit et open-source de ressources pour le C et le C++.
Il y a aussi de la précision numérique des bibliothèques JavaScript(!) et je pense que PHP qui permet de traiter les calculs financiers.
Il y a aussi des propriétaires (surtout, je pense, pour Fortran) et les solutions open-source ainsi pour de nombreux langages informatiques.
Je ne suis pas informaticien de formation. Cependant, j'ai tendance à pencher vers soit BigDecimal en Java ou en virgule en C#. Je n'ai pas essayé les autres solutions que j'ai mentionnés, mais ils sont sans doute très bien ainsi.
Pour moi, j'aime BigDecimal à cause des méthodes qu'il soutient. C#'s décimal est très agréable, mais je n'ai pas eu la chance de travailler avec elle autant que je le voudrais. Je fais des calculs scientifiques d'intérêt pour moi dans mon temps libre, et BigDecimal semble fonctionner très bien, parce que je peux régler la précision de mes nombres en virgule flottante. L'inconvénient de BigDecimal? Il peut être lent à certains moments, surtout si vous êtes à l'aide de la division de la méthode.
Vous pourriez, pour la vitesse, regarder dans la gratuit et la propriété des bibliothèques en C, C++ et Fortran.
À ajouter sur les réponses précédentes, il y a aussi l'option de la mise en œuvre de Joda-Argent en Java, en plus de BigDecimal, lors du traitement du problème abordé dans la question. Java nom de module est org.joda.de l'argent.
Il nécessite Java SE 8 ou plus et n'a pas de dépendances.
Des exemples d'utilisation de Joda de l'Argent:
Quelques exemple... cela fonctionne (en fait ne pas fonctionner comme prévu), sur presque n'importe quel langage de programmation... j'ai essayé avec Delphi, VBScript, Visual Basic, JavaScript et maintenant avec Java/Android:
De SORTIE:
round problems?: current total: 0.9999999999999999
round problems?: current total: 2.7755575615628914E-17
round problems?: is total equal to ZERO? NO... thats why you should not use Double for some math!!!