Convertir float en double perd en précision mais pas via ToSsortingng

J’ai le code suivant:

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

Les résultats sont équivalents à:

 d1 = 0.30000001192092896; d2 = 0.3; 

Je suis curieux de savoir pourquoi c’est?

Ce n’est pas une perte de précision .3 n’est pas représentable en virgule flottante . Lorsque le système se 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.ToSsortingng("G20")); ssortingng s = ssortingng.Format("d1 : {0} ; d2 : {1} ", d1, d2); 

sortie

 "d1 : 0.300000011920929 ; d2 : 0.300000012 " 

Tu ne perds pas en précision; vous optez pour une représentation plus précise (double, longue de 64 bits) à partir d’une représentation moins précise (float, longue de 32 bits). Ce que vous obtenez dans la représentation plus précise (au-delà d’un certain point) n’est que des ordures. Si vous le remettiez dans un float FROM un double, vous auriez exactement la même précision qu’auparavant.

Qu’est-ce qui se passe ici est que vous avez 32 bits alloués pour votre float. Vous effectuez ensuite une conversion en un double en ajoutant 32 bits supplémentaires pour représenter votre nombre (pour un total de 64). Ces nouveaux bits sont les moins significatifs (les plus éloignés à droite de votre virgule décimale) et n’ont aucune incidence sur la valeur réelle car ils étaient indéterminés auparavant. En conséquence, ces nouveaux bits ont les valeurs qu’ils ont lors de votre upcast. Ils sont aussi indéterminés qu’ils l’étaient auparavant – des ordures, en d’autres termes.

Lorsque vous passez d’un double à un float, les bits les moins significatifs sont supprimés, ce qui vous laisse 0,300000 (précision à 7 chiffres).

Le mécanisme de conversion d’une chaîne en un float est différent; le compilateur doit parsingr la signification sémantique de la chaîne de caractères ‘0.3f’ et déterminer le lien avec une valeur en virgule flottante. Cela ne peut pas être fait avec un transfert de bits comme la conversion flottante / double – donc, la valeur que vous attendez.

Pour plus d’informations sur le fonctionnement des nombres en virgule flottante, vous pouvez vous intéresser à cet article de Wikipédia sur la norme IEEE 754-1985 (qui contient des images utiles et une bonne explication de la mécanique des choses), et cet article de wiki sur les mises à jour. à la norme en 2008.

modifier:

Tout d’abord, comme @phoog l’a souligné ci-dessous, passer d’une position flottante à une double n’est pas aussi simple que d’append 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 obtenez votre total de 64 bits pour le double.

De plus, en suggérant qu’il y a des ‘ordures’ dans ces endroits les moins significatifs, une généralisation grossière, et probablement pas correcte pour C #. Un peu d’explication et quelques 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 serait compilé dans un binary en langage machine, les compilateurs (au moins les compilateurs C et C ++) n’appendaient aucune instruction de la CPU pour «effacer» ou initialiser la valeur en mémoire variable. Ainsi, à moins que le programmeur n’initialise explicitement une variable à une valeur, les valeurs des bits réservés pour cet emplacement conservent la valeur qu’elles avaient avant la réservation de cette mémoire.

En mode .NET, votre langage C # ou autre langage .NET est compilé en un langage intermédiaire (CIL, Common Intermediate Language), qui est ensuite compilé Just-In-Time par le CLR pour s’exécuter en tant que code natif. Une étape d’initialisation de variable peut être ajoutée ou non par le compilateur C # ou le compilateur JIT. Je ne suis pas sûr.

Voici ce que je sais:

  • J’ai testé cela en jetant le flotteur sur trois doubles différents. Chacun des résultats avait exactement la même valeur.
  • Cette valeur était exactement la même que celle 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 la conversion ascendante soit déterministe (et non en réalité des bits de mémoire), ce qui indique que .NET fait la même chose sur toutes nos machines. Il est toujours vrai de dire que les chiffres supplémentaires ne sont ni plus ni moins précis qu’auparavant, car 0,3f n’est pas exactement égal à 0,3 – il est égal à 0,3 et jusqu’à sept chiffres de précision. Nous ne soaps rien des valeurs des chiffres supplémentaires au-delà des sept premiers.

J’utilise la dissortingbution décimale pour obtenir un résultat correct dans ce cas et le même autre cas

 float ff = 99.95f; double dd = (double)(decimal)ff;