Stocker de l'argent dans une colonne décimale - quelle précision et quelle échelle?

173

J'utilise une colonne décimale pour stocker les valeurs monétaires sur une base de données, et aujourd'hui je me demandais quelle précision et quelle échelle utiliser.

Étant donné que les colonnes supposées char d'une largeur fixe sont plus efficaces, je pensais que la même chose pourrait être vraie pour les colonnes décimales. C'est ça?

Et quelle précision et quelle échelle dois-je utiliser? Je pensais précision 24/8. Est-ce exagéré, insuffisant ou correct?


Voici ce que j'ai décidé de faire:

  • Stockez les taux de conversion (le cas échéant) dans la table de transaction elle-même, sous forme de flottant
  • Stocker la devise dans la table de compte
  • Le montant de la transaction sera un DECIMAL(19,4)
  • Tous les calculs utilisant un taux de conversion seront gérés par mon application afin que je garde le contrôle des problèmes d'arrondi

Je ne pense pas qu'un flottant pour le taux de conversion soit un problème, car c'est principalement à titre de référence, et je vais de toute façon le convertir en décimale.

Merci à tous pour votre précieuse contribution.

Ivan
la source
2
Posez-vous la question: est-il vraiment nécessaire de stocker les données sous forme décimale? Je ne peux pas stocker les données sous forme de centimes / centimes -> entiers?
Terence le
5
DECIMAL(19, 4) est un choix populaire vérifier ceci également vérifier ici World Currency Formats pour décider du nombre de décimales à utiliser, l'espoir vous aidera.
shaijut

Réponses:

181

Si vous recherchez une taille unique, je suggère que ce DECIMAL(19, 4)soit un choix populaire (un rapide Google le confirme). Je pense que cela provient de l'ancien type de données VBA / Access / Jet Currency, étant le premier type décimal à virgule fixe dans la langue; Decimalseulement venu dans le style «version 1.0» (c'est-à-dire pas entièrement implémenté) dans VB6 / VBA6 / Jet 4.0.

La règle de base pour le stockage des valeurs décimales à virgule fixe est de stocker au moins une décimale de plus que ce dont vous avez réellement besoin pour permettre l'arrondi. L'une des raisons de mapper l'ancien Currencytype dans le front-end au DECIMAL(19, 4)type dans le back-end était que les Currencybanquiers étaient arrondis par nature, alors que DECIMAL(p, s)arrondis par troncature.

Une décimale supplémentaire dans le stockage pour DECIMALpermet à un algorithme d'arrondi personnalisé d'être implémenté plutôt que de prendre la valeur par défaut du fournisseur (et l'arrondi des banquiers est alarmant, c'est le moins qu'on puisse dire, pour un concepteur qui s'attend à ce que toutes les valeurs se terminant par 0,5 arrondissent à zéro) .

Oui, DECIMAL(24, 8)ça me semble exagéré. La plupart des devises sont cotées avec quatre ou cinq décimales. Je connais des situations où une échelle décimale de 8 (ou plus) est requise, mais c'est là qu'un montant monétaire `` normal '' (disons quatre décimales) a été au prorata, ce qui implique que la précision décimale doit être réduite en conséquence (considérez également un type à virgule flottante dans de telles circonstances). Et personne n'a autant d'argent aujourd'hui pour exiger une précision décimale de 24 :)

Cependant, au lieu d'une approche universelle, certaines recherches peuvent s'avérer nécessaires. Renseignez-vous auprès de votre concepteur ou expert du domaine sur les règles comptables qui peuvent être applicables: GAAP, UE, etc. Je me souviens vaguement de certains transferts intra-étatiques de l'UE avec des règles explicites pour arrondir à cinq décimales, donc utiliser DECIMAL(p, 6)pour le stockage. Les comptables semblent généralement privilégier quatre décimales.


PS Évitez le MONEYtype de données de SQL Server car il a de sérieux problèmes de précision lors de l'arrondi, entre autres considérations telles que la portabilité, etc. Voir le blog d'Aaron Bertrand .


Microsoft et les concepteurs de langage ont choisi l'arrondissement des banquiers parce que les concepteurs de matériel l'ont choisi [citation?]. Il est par exemple inscrit dans les normes de l'Institut des ingénieurs électriciens et électroniciens (IEEE). Et les concepteurs de matériel l'ont choisi parce que les mathématiciens le préfèrent. Voir Wikipedia ; pour paraphraser: L'édition 1906 de Probability and Theory of Error appelait cela «la règle de l'ordinateur» («ordinateurs» signifiant les humains qui effectuent des calculs).

un jour quand
la source
1
Consultez cette réponse et cette page pour en savoir plus sur les raisons pour lesquelles les concepteurs de langage choisissent l'arrondi de Banker.
Nick Chammas
1
un jour où: l'arrondissement du banquier n'est pas dû à Microsoft. @NickChammas: ce n'est pas non plus une invention d'un concepteur de langage. Microsoft et les concepteurs de langage l'ont essentiellement choisi parce que les concepteurs de matériel l'ont choisi; elle est inscrite dans les normes de l'Institut des ingénieurs électriciens et électroniciens (IEEE), par exemple. Et les concepteurs de matériel l'ont choisi parce que les mathématiciens le préfèrent. Voir en.wikipedia.org/wiki/Rounding#History ; pour paraphraser: L'édition 1906 de Probability and Theory of Error appelait cela «la règle de l'ordinateur» («ordinateurs» signifiant les humains qui effectuent des calculs).
phoog
9
alors que dois-je utiliser pour Bitcoin?
Boîte à outils du
1
@onedayquand pourquoi est-il DECIMAL(19, 4)plus populaire que DECIMAL(19, 2)? La plupart des devises mondiales ne comportent que deux décimales.
cokedude
3
@ zypA13510: oui, mon affirmation remonte à il y a dix ans! Mais ceci est mon troisième plus voté pour la réponse et représente un morceau de juste de changement en ce qui concerne mon représentant SO donc je suis chiques dans :)
onedaywhen
105

Nous avons récemment mis en œuvre un système qui doit gérer les valeurs dans plusieurs devises et effectuer une conversion entre elles, et nous avons résolu certaines choses à la dure.

NE JAMAIS UTILISER DE NUMÉROS DE POINTS FLOTTANTS POUR DE L'ARGENT

L'arithmétique en virgule flottante introduit des inexactitudes qui peuvent ne pas être remarquées tant qu'ils n'ont pas foiré quelque chose. Toutes les valeurs doivent être stockées sous forme d'entiers ou de types décimaux fixes, et si vous choisissez d'utiliser un type décimal fixe, assurez-vous de comprendre exactement ce que fait ce type sous le capot (c'est-à-dire, utilise-t-il en interne un entier ou une virgule flottante type).

Lorsque vous devez effectuer des calculs ou des conversions:

  1. Convertir les valeurs en virgule flottante
  2. Calculer une nouvelle valeur
  3. Arrondissez le nombre et reconvertissez-le en entier

Lors de la conversion d'un nombre à virgule flottante en un entier à l'étape 3, ne vous contentez pas de le convertir - utilisez une fonction mathématique pour l'arrondir en premier. Ce sera généralement le cas round, bien que dans des cas particuliers, cela puisse être floorou ceil. Connaissez la différence et choisissez avec soin.

Stocker le type d'un nombre à côté de la valeur

Cela peut ne pas être aussi important pour vous si vous ne manipulez qu'une seule devise, mais c'était important pour nous dans le traitement de plusieurs devises. Nous avons utilisé le code à 3 caractères pour une devise, telle que USD, GBP, JPY, EUR, etc.

Selon la situation, il peut également être utile de stocker:

  • Si le numéro est avant ou après impôt (et quel était le taux d'imposition)
  • Si le nombre est le résultat d'une conversion (et de quoi il a été converti)

Connaissez les limites de précision des nombres avec lesquels vous traitez

Pour les valeurs réelles, vous voulez être aussi précis que la plus petite unité de la devise. Cela signifie que vous n'avez pas de valeurs inférieures à un cent, un centime, un yen, un fen, etc. Ne stockez pas les valeurs avec une précision plus élevée que cela sans raison.

En interne, vous pouvez choisir de traiter des valeurs plus petites, auquel cas il s'agit d'un type de valeur de devise différent . Assurez-vous que votre code sait lequel est lequel et ne les confond pas. Évitez d'utiliser des valeurs à virgule flottante même ici.


En ajoutant toutes ces règles ensemble, nous avons décidé des règles suivantes. Dans le code en cours d'exécution, les devises sont stockées en utilisant un entier pour la plus petite unité.

class Currency {
   String code;       //  eg "USD"
   int value;         //  eg 2500
   boolean converted;
}

class Price {
   Currency grossValue;
   Currency netValue;
   Tax taxRate;
}

Dans la base de données, les valeurs sont stockées sous forme de chaîne au format suivant:

USD:2500

Cela stocke la valeur de 25,00 $. Nous avons pu le faire uniquement parce que le code qui traite des devises n'a pas besoin d'être dans la couche de base de données elle-même, de sorte que toutes les valeurs peuvent être converties en mémoire en premier. D'autres situations se prêteront sans doute à d'autres solutions.


Et au cas où je ne l'ai pas précisé plus tôt, n'utilisez pas de flotteur!

Marcus Downing
la source
1
Ne dites jamais jamais: parfois, les montants d'argent sont au prorata et doivent s'additionner plus tard. Exemple: diviser le dividende total (relativement petit) divisé par le nombre d'actions en circulation (relativement petit) pour donner un net par action. Parfois, le flotteur tourne mieux :)
quand
23
Je soutiens mon jamais. La spécification en virgule flottante comporte des inexactitudes qui s'ajouteront au fur et à mesure que vous effectuez de calculs. Si vous avez besoin de stocker des valeurs inférieures à un cent ou un centime, définissez le niveau de précision dont vous avez besoin et respectez-le. N'utilisez pas de flotteur. Sérieusement. C'est une mauvaise idée.
Marcus Downing
4
Cette réponse s'aligne également sur les meilleures pratiques javascript décrites par Douglas Crockford dans sa "série Crockford sur JavaScript", où il recommande de faire tous les calculs de devises en PENNIES pour éviter les erreurs de machine lors de l'arrondi. Donc, si vous travaillez avec des devises en javascript, il est très logique de stocker la valeur de cette manière.
paperreduction
1
@onedaywhen Ces cas nets par action, vous pouvez additionner les montants au prorata et les comparer au total d'origine et concevoir une stratégie pour traiter le reste (lors de l'utilisation de types décimaux / entiers).
Shiv
2
Pour Mysql, je recommande de stocker l'entier (2500) en tant que bigintsi vous avez l'intention de trier par montant. Et ne perdez pas votre temps avec PHP 32 bits lorsque vous traitez avec de gros entiers, passez à 64 bits ou Node.JS;)
Ricky Boyce
4

Lorsque vous manipulez de l'argent dans MySQL, utilisez DECIMAL (13,2) si vous connaissez la précision de vos valeurs d'argent ou utilisez DOUBLE si vous voulez juste une valeur approximative rapide assez bonne. Donc, si votre application doit gérer des valeurs monétaires allant jusqu'à un billion de dollars (ou euros ou livres), cela devrait fonctionner:

DECIMAL(13, 2)

Ou, si vous devez vous conformer aux PCGR, utilisez:

DECIMAL(13, 4)
pollux1er
la source
3
Pouvez-vous créer un lien vers la partie spécifique des directives PCGR au lieu du contenu d'un document de 2500 pages? Merci.
ReactingToAngularVues
@ReactingToAngularVues, ​​il semble que la page ait changé. Désolé
pollux1er
2

4 décimales vous donneraient la précision de stocker les plus petites sous-unités monétaires du monde. Vous pouvez le réduire davantage si vous avez besoin d'une précision de micropaiement (nanopaiement?!).

Moi aussi, je préfère DECIMALles types d'argent spécifiques aux SGBD, vous êtes plus sûr de garder ce genre de logique dans l'application IMO. Une autre approche dans le même sens consiste simplement à utiliser un entier [long], avec un formatage en ¤unit.subunit pour une lisibilité humaine (¤ = symbole monétaire) effectué au niveau de l'application.

bobince
la source
1

Le type de données money sur SQL Server comporte quatre chiffres après la virgule.

À partir de la documentation en ligne de SQL Server 2000:

Les données monétaires représentent des montants d'argent positifs ou négatifs. Dans Microsoft® SQL Server ™ 2000, les données monétaires sont stockées à l'aide des types de données money et smallmoney. Les données monétaires peuvent être stockées avec une précision de quatre décimales. Utilisez le type de données money pour stocker des valeurs comprises entre -922 337 203 685 477,5808 et +922 337 203 685 477,5807 (nécessite 8 octets pour stocker une valeur). Utilisez le type de données smallmoney pour stocker des valeurs comprises entre -214 748,3648 et 214 748,3647 (nécessite 4 octets pour stocker une valeur). Si un plus grand nombre de décimales est requis, utilisez plutôt le type de données décimal.

Austin Salonen
la source
1

Parfois, vous devrez aller à moins d'un cent et il y a des devises internationales qui utilisent de très grandes démoniations. Par exemple, vous pouvez facturer à vos clients 0,088 centimes par transaction. Dans ma base de données Oracle, les colonnes sont définies comme NUMBER (20,4)

WW.
la source
1

Si vous envisagez d'effectuer des opérations arithmétiques dans la base de données (multipliant les taux de facturation, etc.), vous voudrez probablement beaucoup plus de précision que ce que les gens suggèrent ici, pour les mêmes raisons que vous ne souhaitez utiliser une valeur inférieure à une valeur à virgule flottante double précision dans le code d'application.

Hank Gay
la source
C'était ce à quoi je pensais, mais en termes de taux de change (c'est-à-dire de convertir les dollars Zimbawe en USD). Je vais effectuer quelques expériences sur les bases de données que j'utilise (psql, sqlite) pour voir comment elles gèrent l'arrondi sur de très petites décimales.
Ivan
De plus, les flottants n'ont-ils pas de problèmes de précision dans certains dbms / langages?
Ivan
2
les flotteurs ont des problèmes de précision dans TOUTES les langues.
Marcus Downing
La recommandation la plus courante de nos jours est d'utiliser la précision arbitraire (pensez BigDecimal), mais pendant longtemps, c'était la double précision (pensezdouble au lieu de float). En outre, la précision arbitraire entraîne des pénalités de performances significatives dans certains cas. Le test est définitivement la bonne approche.
Hank Gay
0

Si vous utilisiez IBM Informix Dynamic Server, vous auriez un type MONEY qui est une variante mineure du type DECIMAL ou NUMERIC. C'est toujours un type à virgule fixe (alors que DECIMAL peut être un type à virgule flottante). Vous pouvez spécifier une échelle de 1 à 32 et une précision de 0 à 32 (par défaut, une échelle de 16 et une précision de 2). Ainsi, en fonction de ce que vous devez stocker, vous pouvez utiliser DECIMAL (16,2) - encore assez grand pour contenir le déficit fédéral américain, au cent près - ou vous pouvez utiliser une plage plus petite ou plus de décimales.

Jonathan Leffler
la source
0

Je pense que dans une large mesure, vos exigences ou celles de votre client devraient dicter la précision et l'échelle à utiliser. Par exemple, pour le site Web de commerce électronique sur lequel je travaille et qui traite uniquement de l'argent en GBP, j'ai été obligé de le maintenir à Decimal (6, 2).

ayaz
la source
0

Une réponse tardive ici, mais j'ai utilisé

DECIMAL(13,2)

ce que j'ai raison de penser devrait permettre jusqu'à 99 999 999 999,99.

Mike Upjohn
la source