Conversion d'un nombre décimal en nombre double en C# entraîne une différence
Résumé du problème:
Pour des valeurs décimales, lorsque nous convertir le type de décimal à double, une petite fraction est ajouté à la suite.
Ce qui est pire, c'est qu'il peut y avoir deux "égalité" des valeurs décimales qui sont le résultat de différentes valeurs double lors de la conversion.
Exemple de Code:
decimal dcm = 8224055000.0000000000m; //dcm = 8224055000
double dbl = Convert.ToDouble(dcm); //dbl = 8224055000.000001
decimal dcm2 = Convert.ToDecimal(dbl); //dcm2 = 8224055000
double dbl2 = Convert.ToDouble(dcm2); //dbl2 = 8224055000.0
decimal deltaDcm = dcm2 - dcm; //deltaDcm = 0
double deltaDbl = dbl2 - dbl; //deltaDbl = -0.00000095367431640625
Regardez les résultats dans les commentaires. Les résultats sont copiés à partir du débogueur regarder.
Les chiffres qui produisent cet effet ont beaucoup moins de chiffres après la virgule que la limite des types de données, de sorte qu'il ne peut pas être un dépassement de capacité (je suppose!).
Ce qui le rend beaucoup plus intéressant, c'est qu'il peut y avoir deux égal les valeurs après la virgule (dans l'exemple de code ci-dessus, voir "dcm" et "dcm2", avec "deltaDcm" égal à zéro) résultant de différentes double des valeurs lors de la conversion. (Dans le code, "dbl" et "dbl2", qui n'est pas nulle "deltaDbl")
J'imagine que ça doit être quelque chose lié à la différence de la bit-à-bit de la représentation des nombres dans les deux types de données, mais ne peut pas comprendre ce qui! Et j'ai besoin de savoir quoi faire pour faire de la conversion de la manière dont j'ai besoin c'est d'être. (comme dcm2 -> dbl2)
- J'ai signalé ce problème sur MS Connecter. Voici le lien: connect.microsoft.com/VisualStudio/feedback/...
- Je ne suis pas sûr de ce qui est la raison, mais il semble que le problème est dans l' (6) un grand nombre de décimales. J'ai testé avec 5 décimales et fonctionne très bien. J'ai même scénario où je transformer un nombre décimal en double et en arrière, et depuis ma précision n'est que de 2 décimales, mon code est sûr converti.
Vous devez vous connecter pour publier un commentaire.
Intéressant - même si en général je n'ai pas confiance en normal façons d'écrire des valeurs à virgule flottante lorsque vous êtes intéressé par le résultat exact.
Ici est un peu plus simple démonstration, à l'aide de
DoubleConverter.cs
que j'ai utilisé à quelques reprises avant de.Résultats:
Maintenant, la question est pourquoi la valeur d'origine (8224055000.0000000000) qui est un entier - et exactement représentable comme un
double
- se termine avec des données supplémentaires. Je soupçonne fortement que c'est en raison de bizarreries dans l'algorithme utilisé pour convertir dedecimal
àdouble
, mais c'est malheureux.Il viole également l'article 6.2.1 de la C# spec:
Le "plus proche du double de la valeur" est clairement 8224055000... donc c'est un bug de l'OMI. Ce n'est pas celui que je m'attends à obtenir fixe tout moment bientôt cependant. (Elle donne les mêmes résultats .NET 4.0b1 par le chemin.)
Pour éviter le bug, vous voulez probablement pour normaliser la valeur décimale d'abord, effectivement "retrait" de l'extra-0 après la virgule. C'est un peu difficile car il implique de 96 bits arithmétique des nombres entiers - le .NET 4.0
BigInteger
classe peuvent le rendre plus facile, mais qui peut ne pas être une option pour vous.decimal
faire le gros du travail 🙂Convert.ToDouble(object)
retourne mauvaises valeurs : 8.4 devient 8.39999..., 9.1 devient 9.1000003... . Mon laid solution de contournement : arrondir les valeurs retournées par la conversion à l'aide de la précision attendue de l'oracle de la valeur.decimal
.Single
decimal
est un meilleur type à utiliser... c'est une honte si la base de données est le processus de conversion àfloat
automatiquement 🙁La réponse réside dans le fait que
decimal
tente de conserver le nombre de chiffres significatifs. Ainsi,8224055000.0000000000m
a 20 chiffres significatifs et est stockée en tant que82240550000000000000E-10
, tandis que8224055000m
a seulement 10 et est stockée en tant que8224055000E+0
.double
's de la mantisse est (logiquement) 53 bits, c'est à dire à plus de 16 chiffres décimaux. C'est exactement la précision que vous obtenez lorsque vous convertir àdouble
, et en effet la divagation des1
dans votre exemple, dans le 16e décimale. La conversion n'est pas 1-de-1, parce quedouble
utilise la base 2.Voici les représentations binaires de vos numéros:
Pour le double, j'ai utilisé des points de délimiter le signe, l'exposant et la mantisse champs; pour les décimales, voir MSDN sur le système décimal.GetBits, mais essentiellement de la dernière 96 bits de la mantisse. Notez comment les bits de la mantisse de
dcm2
et les bits les plus significatifs dedbl2
coïncident exactement (ne pas oublier l'implicite1
peu dansdouble
's mantisse), et, en fait, ces bits représentent 8224055000. Les bits de la mantisse dedbl
sont les mêmes que dansdcm2
etdbl2
mais pour le méchant1
dans le bit le moins significatif. L'exposant dedcm
est de 10, et la mantisse est 82240550000000000000.Mise à jour II: Il est effectivement très facile à lop off zéros à droite.
L'article Ce Que Tout Informaticien Devez Savoir À Propos De L'Arithmétique À Virgule Flottante serait un excellent endroit pour commencer.
La réponse courte est que virgule flottante binaire arithmétique est nécessairement un rapprochement, et il n'est pas toujours le rapprochement vous dirais. C'est parce que les Processeurs ne l'arithmétique en base 2, alors que l'homme (en général) de faire de l'arithmétique en base 10. Il y a une grande variété d'effets inattendus qui en découlent.
Decimal.ToString()
fidèlement vous montre les zéros à droite, de sorte que vous pouvez distinguer les "égaux mais pas identiques" décimales par que, même en VS du débogueur montre normale etc. de windows.De voir que ce problème plus clairement illustré essayer dans la LinqPad (ou remplacer tous les .Dump()'s et changer de Console.WriteLine()s, si vous avez envie).
Il semble logiquement incorrecte pour moi que la précision des décimales pourrait résultat en 3 doubles. Bravo à @AntonTykhyy pour l' /PreciseOne idée:
Decimal
àdouble
est effectuée par la division d'un entier par une puissance de dix, même si ce peut introduire une erreur d'arrondi.C'est un vieux problème, et a été l'objet de beaucoup de questions similaires sur StackOverflow.
La simpliste explication est que les nombres décimaux ne peuvent pas être exactement représenté en binaire
Ce lien est un article qui pourrait expliquer le problème.
decimal
àdouble
de conversion