Stockez l'argent en cents entiers, jamais en nombre flottant
L'affirmation Un nombre à virgule flottante ne peut pas représenter dix cents exactement ; tout système qui stocke l'argent dans une colonne flottante finira donc, après assez de t...
L'affirmation
Un nombre à virgule flottante ne peut pas représenter dix cents exactement ; tout système qui stocke l'argent dans une colonne flottante finira donc, après assez de transactions, par produire des totaux faux d'un cent — et un cent que personne ne peut expliquer coûte un après-midi à un teneur de livres et vous coûte sa confiance dans le système. Stockez l'argent en compte entier de la plus petite unité monétaire, ou en décimal à précision fixe. Jamais en flottant. Ce n'est pas une préférence ; c'est de l'arithmétique.
Prouvez-le-vous en dix secondes
Ouvrez la console de n'importe quel langage :
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.1 + 0.1 == 0.3
False
Ce n'est pas un défaut du langage. C'est le fonctionnement de la virgule flottante binaire : 0,1 n'a pas de représentation exacte en base 2, exactement comme 1/3 n'en a pas en base 10. L'erreur est minuscule par opération, mais elle s'accumule, et elle s'accumule précisément dans le sens d'un écart que vous ne pouvez pas arrondir, faute de savoir laquelle de mille additions l'a introduit.
Où cela fait mal en pratique
Prenez une facture de 37 postes, chacun stocké en flottant. Additionnez-les, appliquez 13 % de TVH, et le total qu'affiche votre application peut différer d'un cent de celui que calcule le système comptable à partir des mêmes données, parce que les deux ont accumulé l'erreur flottante dans un ordre différent. La facture payée d'un client affiche alors un solde d'un cent, votre relance automatique le lui réclame, et vous avez dépensé de l'argent et de la bonne volonté sur un artefact d'arrondi.
Le cas de la taxe est pire, car les autorités fiscales imposent des règles d'arrondi, et « le flottant a arrondi dans l'autre sens » n'est pas une défense en vérification.
Les deux représentations correctes
Unités mineures entières. Stockez 19,99 $ comme l'entier 1999, en cents. Toute l'arithmétique est entière, donc exacte. C'est compact, rapide et sans ambiguïté, et c'est ce qu'utilisent la plupart des fournisseurs de paiement dans leurs API, précisément pour cette raison.
CREATE TABLE line_items (
amount_cents bigint NOT NULL, -- 1999 = 19,99 $
currency char(3) NOT NULL -- 'CAD'
);
Décimal à précision fixe. Le type numeric(12,2) de Postgres stocke la valeur comme un décimal exact, pas un flottant. L'arithmétique est exacte à l'échelle déclarée. Cela se lit naturellement dans les requêtes et convient quand il faut plus de deux décimales — des prix unitaires à quatre décimales pour une tarification au gramme, par exemple.
CREATE TABLE prices (
unit_price numeric(12,4) NOT NULL -- exact, 4 decimales
);
Les deux sont corrects. Choisissez les cents entiers quand chaque montant a la même échelle à deux décimales et que vous voulez la vitesse ; choisissez le numérique quand les échelles varient ou que la lisibilité prime la performance marginale.
La devise doit voyager avec le montant
Un montant sans devise est un nombre, pas de l'argent. 1999 vaut dix-neuf dollars et quatre-vingt-dix-neuf cents au Canada et tout autre chose dans une devise sans unité mineure — le yen japonais n'a pas de cents, son « unité mineure » est donc le yen lui-même, et 1999 signifie 1999 yens. Stockez le code de devise à côté de chaque montant, et n'additionnez jamais deux montants sans confirmer d'abord qu'ils la partagent. Un total qui somme en silence des CAD et des USD est pire qu'un plantage, car il ressemble à une réponse.
L'arrondi est une décision : prenez-la une seule fois
Quand vous devez arrondir — répartir un total entre des postes, calculer un pourcentage — la règle doit être explicite et appliquée à un seul endroit du code. L'erreur classique est d'arrondir chaque poste puis d'additionner, ce qui ne vaut pas arrondir la somme : trois postes à 0,335 $ arrondissent à 0,34 $ chacun, soit 1,02 $, alors que la vraie somme de 1,005 $ arrondit à 1,01 $. Décidez ce qu'exigent votre entreprise et votre fisc, écrivez-le dans une fonction, et faites passer chaque arrondi monétaire par elle.
// une fonction, partout ; arrondi bancaire montre
function roundCents(amount) { /* arrondi a la paire */ }
Réparer un système qui utilisait déjà des flottants
Si l'argent vit déjà dans des colonnes flottantes, les valeurs sont déjà imprécises ; vous ne pouvez donc pas simplement changer le type de colonne et faire confiance au contenu. Convertissez avec une étape d'arrondi explicite, rapprochez les totaux convertis d'une source faisant autorité — le relevé du mois dernier rapproché de la banque est idéal — et ne basculez qu'ensuite l'application vers l'arithmétique entière ou numérique. La migration est simple ; le rapprochement est ce qui compte, car c'est votre unique chance de trouver les erreurs que les flottants ont déjà introduites avant de les figer dans un type exact et d'en hériter pour toujours.