web-dev-qa-db-fra.com

Perte de précision C # double à décimale

J'ai un double "138630.78380386264" Et je veux le convertir en décimal, mais quand je le fais, je le fais soit en transtypant, soit en utilisant Convert.ToDecimal() et je perds en précision.

Que se passe-t-il? Décimal et double peuvent contenir ce nombre:

enter image description here

double doub = double.Parse("138630.78380386264");
decimal dec = decimal.Parse("138630.78380386264");
string decs = dec.ToString("F17");
string doubse =DoubleConverter.ToExactString(doub);
string doubs = doub.ToString("F17");

decimal decC = (decimal) doub;
string doudeccs = decC.ToString("F17");
decimal decConv = Convert.ToDecimal(doub);
string doudecs = decConv.ToString("F17");

Aussi: comment puis-je obtenir la ToString() sur double pour imprimer le même résultat que le débogueur montre? par exemple. 138630.78380386264?

30
GreyCloud

138630.78380386264 N'est pas exactement représentable en double précision. Le nombre de double précision le plus proche (comme trouvé ici ) est 138630.783803862635977566242218017578125, Ce qui correspond à vos résultats.

Vous demandez pourquoi la conversion en décimal ne contient pas plus de précision. la documentation de Convert.ToDecimal() a la réponse:

La valeur décimale renvoyée par cette méthode contient un maximum de 15 chiffres significatifs. Si le paramètre de valeur contient plus de 15 chiffres significatifs, il est arrondi en utilisant l'arrondi au plus proche. L'exemple suivant illustre comment la méthode Convert.ToDecimal (Double) utilise l'arrondi au plus proche pour renvoyer une valeur décimale avec 15 chiffres significatifs.

La valeur double, arrondie au plus près à 15 chiffres significatifs est 138630.783803863, Exactement comme vous l'avez montré ci-dessus.

23
David Heffernan

C'est dommage, je pense. Près de 139 000, un Decimal a une bien meilleure précision qu'un Double. Mais encore, à cause de ce problème, nous avons différentDoubles projeté sur le mêmeDecimal. Par exemple

double doub1 = 138630.7838038626;
double doub2 = 138630.7838038628;
Console.WriteLine(doub1 < doub2);                    // true, values differ as doubles
Console.WriteLine((decimal)doub1 < (decimal)doub2);  // false, values projected onto same decimal

En fait, il y a six différentes valeurs Double représentables entredoub1 et doub2 ci-dessus, donc ce ne sont pas les mêmes.

Voici un travail-idiot quelque peu idiot:

static decimal PreciseConvert(double doub)
{
  // Handle infinities and NaN-s first (throw exception)
  // Otherwise:
  return Decimal.Parse(doub.ToString("R"), NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint);
}

Le "R" la chaîne de format garantit que suffisamment de chiffres supplémentaires sont inclus pour rendre le mappage injectif (dans le domaine où Decimal a une précision supérieure).


Notez que dans une certaine plage, un long (Int64) a une précision supérieure à celle de Double. J'ai donc vérifié si les conversions ici sont effectuées de la même manière (premier arrondi à 15 décimales significatives). Ils ne sont pas! Alors:

double doub3 = 1.386307838038626e18;
double doub4 = 1.386307838038628e18;

Console.WriteLine(doub3 < doub4);              // true, values differ as doubles
Console.WriteLine((long)doub3 < (long)doub4);  // true, full precision of double used when converting to long

Il semble incohérent d'utiliser une "règle" différente lorsque la cible est decimal.

Notez qu'à cause de cela, (decimal)(long)doub3 produit un résultat plus précis que simplement (decimal)doub3.

5