Je peux trouver de nombreuses questions sur les bibliothèques à utiliser pour représenter des montants dans une certaine devise. Et sur la question séculaire de pourquoi vous ne devriez pas stocker de devises sous forme de nombre à virgule flottante IEEE 754. Mais je n'arrive pas à trouver autre chose. Il y a sûrement beaucoup plus à savoir sur la monnaie dans l'utilisation du monde réel. Je suis particulièrement intéressé par ce que vous devez savoir pour le représenter dans un usage physique (par exemple, avec le dollar, vous n'avez jamais une précision inférieure à 0,01 $, permettant la représentation sous la forme d'un nombre entier de cents).
Mais il est difficile de faire des hypothèses sur la polyvalence de votre programme lorsque les seules devises que vous connaissez sont les devises occidentales populaires (telles que le dollar, l'euro et la livre). Quelles sont les autres connaissances pertinentes dans une perspective purement programmatique? Je ne suis pas préoccupé par le sujet de la conversion.
En particulier, que devons-nous savoir pour pouvoir stocker des valeurs dans une certaine devise et les imprimer?
Réponses:
Oh vraiment?
N'hésitez pas à stocker les pouces en nombres à virgule flottante IEEE 754 . Ils stockent précisément ce que vous attendez.
N'hésitez pas à stocker toute somme d'argent en nombres à virgule flottante IEEE 754 que vous pouvez stocker en utilisant les graduations qui divisent une règle en fractions de pouce.
Pourquoi? Parce que lorsque vous utilisez IEEE 754, c'est comme ça que vous le stockez.
Le problème avec les pouces, c'est qu'ils sont divisés en deux. La chose à propos de la plupart des types de devises est qu'elles sont divisées en dixièmes (certaines ne le sont pas, mais restons concentrés).
Cette différence ne serait pas si confuse, sauf que, pour la plupart des langages de programmation, l'entrée et la sortie des nombres à virgule flottante IEEE 754 sont exprimées en décimales! Ce qui est très étrange car ils ne sont pas stockés en décimales.
Pour cette raison, vous ne voyez jamais comment les bits font des choses étranges lorsque vous demandez à l'ordinateur de les stocker
0.1
. Vous ne voyez l'étrangeté que lorsque vous faites des calculs contre cela et il y a d'étranges erreurs.De la java efficace de Josh Bloch :
Produit
0.6100000000000001
Ce qui est le plus révélateur, ce n'est pas le
1
chemin assis là-bas à droite. Ce sont les chiffres étranges qui ont dû être utilisés pour l'obtenir. Plutôt que d'utiliser l'exemple le plus populaire0.1
, nous devons utiliser un exemple qui montre le problème et évite l'arrondi qui le cacherait.Par exemple, pourquoi cela fonctionne-t-il?
Produit
-0.01
Parce que nous avons eu de la chance.
Je déteste les problèmes difficiles à diagnostiquer car j'ai parfois la «chance».
IEEE 754 ne peut tout simplement pas stocker 0,1 avec précision. Mais si vous lui demandez de stocker 0,1 puis de l'imprimer, il affichera 0,1 et vous penserez que tout va bien. Ce n'est pas bien, mais vous ne pouvez pas le voir car il est arrondi pour revenir à 0,1.
Certaines personnes confondent les autres en appelant ces écarts des erreurs d'arrondi. Non, ce ne sont pas des erreurs d'arrondi. L'arrondi consiste à faire ce qu'il est censé faire et à transformer ce qui n'est pas un nombre décimal en un nombre décimal afin qu'il puisse s'imprimer à l'écran.
Mais cela masque l'inadéquation entre la façon dont le numéro est affiché et la façon dont il est stocké. L'erreur ne s'est pas produite lors de l'arrondi. Cela s'est produit lorsque vous avez décidé de mettre un nombre dans un système qui ne peut pas le stocker avec précision et que vous avez supposé qu'il était stocké précisément quand il ne l'était pas.
Personne ne s'attend à ce que π soit stocké avec précision dans une calculatrice et ils parviennent à l'utiliser très bien. Le problème n'est donc même pas lié à la précision. Il s'agit de la précision attendue. Les ordinateurs affichent un dixième de
0.1
la même manière que nos calculatrices, nous nous attendons donc à ce qu'ils stockent un dixième parfaitement comme le font nos calculatrices. Ils ne le font pas. Ce qui est surprenant, car les ordinateurs sont plus chers.Permettez-moi de vous montrer le décalage:
Notez que 1/2 et 0,5 s'alignent parfaitement. Mais 0,1 ne correspond tout simplement pas. Bien sûr, vous pouvez vous rapprocher si vous continuez à diviser par 2, mais vous ne le frapperez jamais exactement. Et nous avons besoin de plus en plus de bits chaque fois que nous divisons par 2. Donc, représenter 0,1 avec tout système qui divise par 2 a besoin d'un nombre infini de bits. Mon disque dur n'est pas si gros.
Donc , IEEE 754 cesse d' essayer quand il court en bits. Ce qui est bien car j'ai besoin de place sur mon disque dur pour ... des photos de famille. Pas vraiment. Photos de famille. : P
Quoi qu'il en soit, ce que vous tapez et ce que vous voyez sont les décimales (à droite) mais ce que vous stockez sont les bicimales (à gauche). Parfois, ce sont parfaitement les mêmes. Parfois non. Parfois, il semble qu'ils sont les mêmes lorsqu'ils ne le sont tout simplement pas. Voilà l'arrondi.
S'il vous plaît, si vous manipulez mon argent décimal, n'utilisez pas de flottants ou de doubles.
Si vous êtes sûr que des dixièmes de centimes ne seront pas impliqués, stockez simplement des centimes. Si vous ne l'êtes pas, déterminez quelle sera la plus petite unité de cette monnaie et utilisez-la. Si vous ne le pouvez pas, utilisez quelque chose comme BigDecimal .
Ma valeur nette ira probablement toujours très bien dans un entier 64 bits, mais des choses comme BigInteger fonctionnent bien pour des projets plus grands que cela. Ils sont juste plus lents que les types natifs.
Trouver comment le stocker n'est que la moitié du problème. N'oubliez pas que vous devez également pouvoir l'afficher. Un bon design séparera ces deux choses. Le vrai problème avec l'utilisation de flotteurs ici, c'est que ces deux choses sont mises ensemble.
la source
Les "bibliothèques" ne sont pas nécessaires sauf si la bibliothèque standard de votre langue manque dans certains types de données, comme je l'expliquerai.
Tout simplement, vous avez besoin d' une décimale à virgule fixe , et non d' une décimale à virgule flottante . Par exemple, la classe BigDecimal de Java peut être utilisée pour stocker un montant en devise. D'autres langages modernes ont des types similaires intégrés, y compris C # et Python . Les implémentations varient, mais elles stockent généralement un nombre sous forme d'entier, l'emplacement décimal étant un membre de données distinct. Cela donne une précision exacte, même lors de l'exécution d'une arithmétique qui donnerait des restes impairs (par exemple, 0.0000001) avec des nombres à virgule flottante IEEE.
Il y a quelques points importants.
Utilisez un type décimal réel plutôt qu'une virgule flottante.
Sachez qu'un montant en devise a deux composantes: une valeur (5,63) et un code ou type de devise (USD, CAD, GBP, EUR, etc.). Parfois, vous pouvez ignorer le code de la devise, d'autres fois, il est vital. Et si vous travaillez sur un système financier ou de vente au détail / e-commerce qui autorise plusieurs devises? Que se passe-t-il si vous essayez de prendre de l'argent d'un client en CAD, mais qu'il souhaite payer avec MXN? Vous avez besoin d'un type "argent" avec un code de devise et un montant de devise pour pouvoir mélanger ces valeurs (également le taux de change, mais je ne veux pas aller trop loin sur une tangente). Dans le même temps, mon logiciel de finances personnelles n'a jamais à s'en soucier car tout est en USD (il peut mélanger les devises, mais je n'en ai jamais besoin).
Alors qu'une devise peut avoir la plus petite unité physique dans la pratique (CAD et USD ont des cents, JPY est juste ... Yen), il est possible de devenir plus petit. La réponse de CandiedOrange indique les prix du carburant en dixièmes de cent. Mes impôts fonciers sont évalués en millièmes de dollar, ou en dixièmes de cent (1/1000 de dollar américain). Ne vous limitez pas à 0,01 $. Bien que vous puissiez afficher ces valeurs la plupart du temps, vos types doivent autoriser des valeurs plus petites (les types décimaux référencés ci-dessus le font).
Les calculs intermédiaires doivent certainement permettre plus de précision qu'un seul centime. J'ai travaillé sur des systèmes de vente au détail / e-commerce où les valeurs internes ont été arrondies à 0,0000000 $ en interne. La précision infinie n'est généralement pas prise en charge par les types décimaux (ou SQL), il doit donc y avoir une certaine limite. Par exemple, diviser 1/3 en utilisant BigDecimal de Java lèvera une exception sans RoundingMode ou MathContext spécifié car la valeur ne peut pas être représentée exactement.
Quoi qu'il en soit, cela est essentiel dans certains cas. Supposons que vous ayez un utilisateur avec six articles dans son panier et qu'il passe à la caisse. Votre système doit calculer la taxe et le fait par article, car les articles peuvent être taxés différemment. Si vous arrondissez les taxes pour chaque article, vous pouvez obtenir des erreurs d'arrondi de centime au niveau de la transaction / du panier. Une approche pour résoudre ce problème pourrait être de stocker les taxes à plus de décimales par article, d'obtenir le total pour la transaction entière, et de revenir en arrière et d'arrondir chaque article pour que la taxe totale soit correcte (peut-être qu'un article arrondit vers le haut, un autre vers le bas).
La chose importante à réaliser de tout cela est que quelque chose d'aussi important que l'arrondissement de pièces de un cent peut être très important pour les bonnes personnes (par exemple certains de mes anciens clients qui ont dû payer les taxes de vente du gouvernement au nom de leurs clients). Cependant, ce sont tous des problèmes résolus. Gardez à l'esprit les points ci-dessus et faites vous-même des expériences, et vous apprendrez en faisant.
la source
Un endroit où de nombreux développeurs sont confrontés à une seule représentation de données pour n'importe quelle devise est celui des achats intégrés pour les applications iOS. Votre client peut être connecté à un magasin dans presque tous les pays du monde. Et dans cette situation, vous recevrez un prix d'achat composé d'un numéro à double précision et d'un code de devise.
Vous devez être conscient que les chiffres pourraient être importants. Il y a des devises où l'équivalent de dix dollars par exemple est supérieur à 100 000 unités. Et nous avons de la chance qu'il n'y ait pas de devises comme le dollar zimbabwéen en ce moment, où vous pourriez avoir des centaines de milliards de billets de banque!
Pour afficher les devises, vous aurez besoin d'une bibliothèque - vous n'avez aucune chance de tout régler vous-même. L'affichage dépend de deux choses: le code de devise et les paramètres régionaux de l'utilisateur. Pensez à la façon dont les dollars américains et les dollars canadiens seraient affichés avec les paramètres régionaux américains et canadiens: aux États-Unis, vous avez $ vs CAN $, et au Canada, vous avez $ US vs $. J'espère que c'est intégré dans le système d'exploitation, ou vous avez une bonne bibliothèque.
Pour les calculs, tout calcul se terminera par une étape d'arrondi. Vous devrez découvrir comment vous devez effectuer cet arrondissement légalement . Ce n'est pas un problème de programmation, c'est un problème juridique. Par exemple, si vous calculez la TVA au Royaume-Uni, vous devez calculer la taxe par article ou par ligne d'article, et l'arrondir à quelques centimes. Ce que vous arrondissez dépend de la devise. Mais les règles dépendent évidemment du pays. Vous ne pouvez pas vous attendre à ce qu'un calcul juridiquement correct au Royaume-Uni soit juridiquement correct au Japon et vice versa.
la source
Quelques exemples de problèmes liés aux paramètres régionaux:
Un autre problème potentiel est que même si vous stockez correctement la devise en nombre à virgule fixe, il peut être nécessaire de la convertir en nombre à virgule flottante car la bibliothèque utilisée pour l'impression prend uniquement en charge les virgules flottantes.
la source