web-dev-qa-db-fra.com

DOUBLE vs DECIMAL dans MySQL

OK, je sais donc que de nombreux articles disent que je ne devrais pas utiliser DOUBLE pour stocker de l’argent sur une base de données MySQL, sinon je finirais avec des bugs de précision délicats. Le fait est que je ne conçois pas une nouvelle base de données, je suis invité à trouver le moyen d’optimiser un système existant. La version la plus récente contient 783 colonnes typées DOUBLE, la plupart d’entre elles étant utilisées pour stocker de l’argent ou des formules pour calculer le montant en argent.

Donc, ma première opinion sur le sujet était de recommander une conversion de DOUBLE en DECIMAL dans la prochaine version, car la documentation MySQL et tout le monde le disent. Mais ensuite, je n'ai trouvé aucun argument valable pour justifier cette recommandation, pour trois raisons:

  • Nous n'effectuons aucun calcul sur la base de données. Toutes les opérations sont effectuées dans Java avec BigDecimal, et MySQL est simplement utilisé comme stockage brut pour les résultats.
  • La précision de 15 chiffres offerte par DOUBLE est suffisante car nous stockons principalement des montants avec 2 chiffres décimaux et parfois de petits nombres avec 8 chiffres décimaux pour les arguments de formule.
  • Nous avons un enregistrement de 6 ans en production sans problème de bug connu dû à une perte de précision du côté de MySQL.

Même en effectuant des opérations sur une table de 18 millions de lignes, comme SUM et des multiplications complexes, je ne pouvais pas exécuter un bug de manque de précision. Et nous ne faisons pas ce genre de choses en production. Je peux montrer la précision perdue en faisant quelque chose comme

SELECT columnName * 1.000000000000000 FROM tableName;

Mais je ne peux pas trouver un moyen de le transformer en un bogue à la 2ème décimale. La plupart des problèmes réels que j'ai trouvés sur Internet concernent les entrées de forum de 2005 et plus, et je ne pouvais en reproduire aucun sur un serveur MySQL 5.0.51.

Donc, tant que nous n’effectuons aucune opération arithmétique SQL, ce que nous n’avons pas l'intention de faire, existe-t-il un problème auquel nous devrions nous attendre lorsque nous stockons et récupérons un montant en argent dans une colonne DOUBLE?

63
user327961

En fait, c'est assez différent. DOUBLE provoque des problèmes d'arrondi. Et si vous faites quelque chose comme 0.1 + 0.2 ça vous donne quelque chose comme 0.30000000000000004. Personnellement, je ne ferais pas confiance aux données financières qui utilisent des mathématiques en virgule flottante. L'impact peut être faible, mais qui sait. Je préférerais avoir ce que je sais être des données fiables plutôt que des données approximatives, en particulier lorsqu'il s'agit de valeurs monétaires.

46
bash-

L'exemple de la documentation MySQL http://dev.mysql.com/doc/refman/5.1/en/problems-with-float.html (je le rétrécis, la documentation de cette section est la même pour 5.5)

mysql> create table t1 (i int, d1 double, d2 double);

mysql> insert into t1 values (2, 0.00  , 0.00),
                             (2, -13.20, 0.00),
                             (2, 59.60 , 46.40),
                             (2, 30.40 , 30.40);

mysql> select
         i,
         sum(d1) as a,
         sum(d2) as b
       from
         t1
       group by
         i
       having a <> b; -- a != b

+------+-------------------+------+
| i    | a                 | b    |
+------+-------------------+------+
|    2 | 76.80000000000001 | 76.8 |
+------+-------------------+------+
1 row in set (0.00 sec)

En gros, si vous faites la somme de vous obtenez 0-13.2 + 59.6 + 30.4 = 76.8. Si nous résumons b, nous obtenons 0 + 0 + 46,4 + 30,4 = 76,8. La somme de a et b est identique, mais la documentation de MySQL indique:

Une valeur à virgule flottante telle qu’elle est écrite dans une instruction SQL peut ne pas être identique à la valeur représentée en interne.

Si on répète la même chose avec décimal:

mysql> create table t2 (i int, d1 decimal(60,30), d2 decimal(60,30));
Query OK, 0 rows  affected (0.09 sec)

mysql> insert into t2 values (2, 0.00  , 0.00),
                             (2, -13.20, 0.00),
                             (2, 59.60 , 46.40),
                             (2, 30.40 , 30.40);
Query OK, 4 rows affected (0.07 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select
         i,
         sum(d1) as a,
         sum(d2) as b
       from
         t2
       group by
         i
       having a <> b;

Empty set (0.00 sec)

Le résultat attendu est un ensemble vide.

Donc, tant que vous n'effectuez aucune opération arithémétique SQL, vous pouvez utiliser DOUBLE, mais je préférerais quand même DECIMAL.

Une autre chose à noter à propos de DECIMAL est l’arrondissement si la fraction est trop grande. Exemple:

mysql> create table t3 (d decimal(5,2));
Query OK, 0 rows affected (0.07 sec)

mysql> insert into t3 (d) values(34.432);
Query OK, 1 row affected, 1 warning (0.10 sec)

mysql> show warnings;
+-------+------+----------------------------------------+
| Level | Code | Message                                |
+-------+------+----------------------------------------+
| Note  | 1265 | Data truncated for column 'd' at row 1 |
+-------+------+----------------------------------------+
1 row in set (0.00 sec)

mysql> select * from t3;
+-------+
| d     |
+-------+
| 34.43 |
+-------+
1 row in set (0.00 sec)
34
broadband

Nous venons de vivre le même problème, mais dans l’inverse. C'est-à-dire que nous stockons des montants en dollars sous DECIMAL, mais nous constatons maintenant que, par exemple, MySQL calculait une valeur de 4,399999999993, mais lorsqu'il stockait cela dans le champ DECIMAL, il le stockait sous la forme 4,38 au lieu de 4,39 comme nous le souhaitions. c'est à. Ainsi, bien que DOUBLE puisse causer des problèmes d’arrondi, il semble que DECIMAL puisse également causer des problèmes de troncature.

16
Mark

De vos commentaires,

le montant de la taxe est arrondi à la 4ème décimale et le prix total arrondi à la 2ème décimale.

En utilisant l’exemple cité dans les commentaires, je pourrais prévoir un cas dans lequel vous réaliserez 400 ventes de 1,47 $. Le chiffre d'affaires avant impôt serait de 588,00 $ et le montant après impôt de 636,51 $ (ce qui représente 48,51 $ en taxes). Toutefois, la taxe de vente de 0,121275 $ * 400 serait de 48,52 $.

C’était une façon, bien que artificielle, de forcer la différence d’un penny.

Je ferais remarquer qu'il y a des formulaires de taxe sur la masse salariale de l'IRS où ils ne se soucient pas de savoir si une erreur est inférieure à un certain montant (si ma mémoire est bonne, 0,50 $).

Votre grande question est la suivante: Est-ce que quelqu'un se soucie de savoir si certains rapports sont déformés d'un sou? vous devriez passer par les efforts de conversion en DECIMAL.

J'ai travaillé dans une banque où une erreur d'un cent était signalée comme un défaut logiciel. J'ai essayé (en vain) de citer les spécifications du logiciel, qui n'exigeaient pas ce degré de précision pour cette application. (Il effectuait de nombreuses multiplications enchaînées.) J'ai également souligné le test d'acceptation de l'utilisateur. (Le logiciel a été vérifié et accepté.)

Hélas, il suffit parfois de faire la conversion. Mais je vous encourage à A) de vous assurer que c'est important pour quelqu'un, puis B) à passer des tests pour montrer que vos rapports sont précis au degré spécifié.

0
rajah9

"y a-t-il un problème auquel nous devrions nous attendre en ne stockant et en récupérant un montant d'argent que dans une colonne DOUBLE?"

Il semble qu'aucune erreur d'arrondi ne puisse être produite dans votre scénario et, le cas échéant, elle serait tronquée par la conversion en BigDecimal.

Donc je dirais non.

Cependant, rien ne garantit que certains changements à l'avenir ne poseront pas de problème.

0
Steve Wellens