Calcul décimal dans SQL Server: maîtriser les arrondis
Évitez les divisions entières, les dépassements intermédiaires et les écarts de facture en définissant les types numériques et les règles d'arrondi.
Déclarer une colonne decimal ne rend pas automatiquement exacts tous les calculs qui l'alimentent. SQL Server évalue les expressions intermédiaires selon les types des opérandes. Une fraction peut disparaître avant l'affectation finale et un agrégat peut dépasser sa capacité même si la colonne cible est assez grande. En facturation, ces erreurs sont trompeuses car les montants restent souvent vraisemblables.
Commencez par la grandeur métier. Montants, taux de change, pourcentages et quantités n'ont pas les mêmes besoins. Dans decimal(p,s), p compte tous les chiffres et s ceux après la virgule. decimal(12,2) laisse dix chiffres pour la partie entière. Choisir les décimales sans calculer la valeur maximale attendue ne suffit pas.
Maîtriser les résultats intermédiaires
Ces expressions montrent pourquoi une conversion finale arrive parfois trop tard.
SELECT 5 / 2 AS IntegerResult,
CAST(5 AS decimal(12,4)) / 2 AS DecimalResult,
CAST(5 / 2 AS decimal(12,4)) AS CastTooLate;
La division entière donne 2. Convertir un opérande avant la division préserve le résultat 2,5. Convertir le résultat entier produit 2,0000 et ne retrouve pas la fraction perdue.
Le principe vaut aussi pour la multiplication. Deux int peuvent provoquer un dépassement avant une conversion extérieure vers bigint. Il faut élargir un opérande avant l'opération. Le type de la colonne de destination ne modifie pas rétroactivement celui du calcul source.
Les expressions decimal reçoivent également une précision et une échelle calculées. Pour une multiplication, la règle initiale utilise p1 + p2 + 1 et additionne les échelles. La précision maximale est 38; des règles supplémentaires peuvent réduire l'échelle ou laisser subsister un risque de dépassement. Une division peut demander beaucoup de décimales. Enchaîner des conversions vers decimal(38,...) ne garantit donc pas une précision illimitée.
Partez du plus grand produit possible entre quantité, prix et taux, puis de l'erreur finale acceptable. Choisissez les types intermédiaires en conséquence et rendez la conversion finale explicite. Lorsque le pilote le permet, fixez aussi la précision et l'échelle des paramètres de l'application au lieu de les laisser dépendre des valeurs reçues.
Choisir le niveau de l'arrondi
Deux règles de facturation cohérentes peuvent aboutir à des totaux différents.
DECLARE @Lines table (Amount decimal(10,3));
INSERT @Lines VALUES (19.995), (19.995);
SELECT SUM(ROUND(Amount, 2)) AS RoundedPerLine,
ROUND(SUM(Amount), 2) AS RoundedInvoice
FROM @Lines;
Arrondir chaque ligne de 19,995 à deux décimales avant addition donne 40,000. Additionner puis arrondir donne 39,990. Le zéro final affiché vient de l'échelle conservée; l'écart réel est d'un centime.
La position de ROUND ne doit pas décider implicitement de la politique comptable. Définissez si taxes et remises se calculent par ligne ou par facture, dans quel ordre, et comment répartir un éventuel reliquat. SQL Server ROUND tranche les égalités en s'éloignant de zéro. Un système utilisant une autre règle peut produire un montant différent avec les mêmes entrées.
Remplacer decimal par float pour éviter un dépassement introduit un autre problème: float représente les nombres de manière binaire et approximative. Cela convient mal à un contrat exigeant une égalité décimale exacte. Des mesures scientifiques peuvent en revanche justifier un type approché. Le besoin doit guider le choix.
Élargir avant l'agrégation
SUM d'une expression int retourne int, même si le résultat est ensuite affecté à bigint.
DECLARE @Counts table (Quantity int);
INSERT @Counts VALUES (2000000000), (2000000000);
SELECT SUM(CONVERT(bigint, Quantity)) AS TotalQuantity
FROM @Counts;
La conversion porte ici sur chaque entrée avant l'agrégation; le total vaut 4 000 000 000. Convertir SUM(Quantity) après coup ne préviendrait pas son dépassement. SUM sur decimal retourne decimal(38,s), mais le nombre disponible de chiffres entiers reste limité.
Précisez également le traitement des valeurs absentes. SUM ignore NULL et retourne NULL sans entrée non NULL. Remplacer ce résultat par zéro peut convenir à certains rapports, mais masquer des données financières manquantes. Un prix inconnu ne signifie pas un prix nul.
Les tests utiles couvrent divisions fractionnaires, valeurs maximales, remboursements négatifs, cas exactement à mi-chemin, entrées vides et rapprochement avec les calculs applicatifs. Examinez les étapes intermédiaires, pas seulement la chaîne finale formatée. La présentation peut cacher une perte de précision, jamais la réparer.
Références techniques: Microsoft Learn: Precision, scale, and length · Microsoft Learn: SUM.