web-dev-qa-db-fra.com

Convertir le flotteur en double perd sa précision mais pas via ToString

J'ai le code suivant:

float f = 0.3f;
double d1 = System.Convert.ToDouble(f);
double d2 = System.Convert.ToDouble(f.ToString());

Les résultats sont équivalents à:

d1 = 0.30000001192092896;
d2 = 0.3;

Je suis curieux de savoir pourquoi c'est?

23
JoshG

Ce n'est pas une perte de précision .3 n'est pas représentable en virgule flottante . Lorsque le système convertit en chaîne, il arrondit; si vous imprimez suffisamment de chiffres significatifs, vous obtiendrez quelque chose de plus logique.

Pour le voir plus clairement

float f = 0.3f;
double d1 = System.Convert.ToDouble(f);
double d2 = System.Convert.ToDouble(f.ToString("G20"));

string s = string.Format("d1 : {0} ; d2 : {1} ", d1, d2);

production

"d1 : 0.300000011920929 ; d2 : 0.300000012 "
24
rerun

Vous ne perdez pas de précision; vous effectuez une conversion ascendante vers une représentation plus précise (double, 64 bits de long) à partir d'une représentation moins précise (float, 32 bits de long). Ce que vous obtenez dans la représentation plus précise (au-delà d'un certain point) n'est que des ordures. Si vous deviez le relancer à un flotteur à partir d'un double, vous auriez exactement la même précision qu'auparavant.

Ce qui se passe ici, c'est que vous avez 32 bits alloués pour votre flotteur. Vous avez ensuite converti en double, ajoutant 32 bits supplémentaires pour représenter votre numéro (pour un total de 64). Ces nouveaux bits sont les moins significatifs (les plus à droite de votre séparateur décimal) et n'ont aucune incidence sur la valeur réelle car ils étaient indéterminés auparavant. Par conséquent, ces nouveaux bits ont toutes les valeurs qu'ils avaient lorsque vous avez effectué votre conversion ascendante. Ils sont aussi indéterminés qu'avant - des ordures, en d'autres termes.

Lorsque vous descendez d'un double à un flottant, il coupera les bits les moins significatifs, vous laissant avec 0,300000 (7 chiffres de précision).

Le mécanisme de conversion d'une chaîne en flottant est différent; le compilateur doit analyser la signification sémantique de la chaîne de caractères "0.3f" et comprendre comment cela se rapporte à une valeur à virgule flottante. Cela ne peut pas être fait avec un décalage de bits comme la conversion float/double - ainsi, la valeur que vous attendez.

Pour plus d'informations sur le fonctionnement des nombres à virgule flottante, vous pouvez être intéressé par la lecture de this article wikipedia sur la norme IEEE 754-1985 (qui contient des images pratiques et une bonne explication de la mécanique des choses), et this article wiki sur les mises à jour de la norme en 2008.

modifier:

Tout d'abord, comme @phoog l'a souligné ci-dessous, la conversion ascendante d'un flottant en un double n'est pas aussi simple que d'ajouter 32 bits supplémentaires à l'espace réservé pour enregistrer le nombre. En réalité, vous obtiendrez 3 bits supplémentaires pour l'exposant (pour un total de 11), et 29 bits supplémentaires pour la fraction (pour un total de 52). Ajoutez le bit de signe et vous avez votre total de 64 bits pour le double.

De plus, ce qui suggère qu'il y a des "morceaux de déchets" dans ces emplacements les moins significatifs, une généralisation grossière, et probablement pas correct pour C #. Un peu d'explication, et certains tests ci-dessous me suggèrent que c'est déterministe pour C # /. NET, et probablement le résultat d'un mécanisme spécifique dans la conversion plutôt que de réserver de la mémoire pour une précision supplémentaire.

Il y a longtemps, lorsque votre code se compilait dans un binaire en langage machine, les compilateurs (compilateurs C et C++, au moins) n'ajoutaient aucune instruction CPU pour `` effacer '' ou initialiser la valeur en mémoire lorsque vous réserviez de l'espace pour un variable. Ainsi, à moins que le programmeur n'initialise explicitement une variable à une certaine valeur, les valeurs des bits réservés pour cet emplacement conserveront la valeur qu'ils avaient avant de réserver cette mémoire.

Dans .NET land, votre langage C # ou autre langage .NET se compile en un langage intermédiaire (CIL, Common Intermediate Language), qui est ensuite compilé juste à temps par le CLR pour s'exécuter en tant que code natif. Il peut y avoir ou non une étape d'initialisation de variable ajoutée par le compilateur C # ou le compilateur JIT; Je ne suis pas sûr.

Voici ce que je sais:

  • J'ai testé cela en lançant le flotteur dans trois doubles différents. Chacun des résultats avait exactement la même valeur.
  • Cette valeur était exactement la même que la valeur de @ rerun ci-dessus: double d1 = System.Convert.ToDouble(f); résultat: d1 : 0.300000011920929
  • J'obtiens le même résultat si je lance en utilisant double d2 = (double)f; Résultat: d2 : 0.300000011920929

Avec trois d'entre nous obtenant les mêmes valeurs, il semble que la valeur de conversion ascendante soit déterministe (et non en fait des bits inutiles), indiquant que .NET fait quelque chose de la même manière sur toutes nos machines. Il est toujours vrai de dire que les chiffres supplémentaires ne sont pas plus ou moins précis qu'auparavant, car 0,3f n'est pas exactement égal à 0,3 - il est égal à 0,3, jusqu'à sept chiffres de précision. Nous ne savons rien des valeurs des chiffres supplémentaires au-delà de ces sept premiers.

14
Joe

J'utilise une fonte décimale pour un résultat correct dans ce cas et dans le même autre cas

float ff = 99.95f;
double dd = (double)(decimal)ff;
4
Amirhosein Karimi