Comment lancer float double sans append de chiffres supplémentaires?

J’ai rencontré un problème intéressant: lorsque je convertis la valeur float -99.9f en une variable double , la valeur de cette variable est -99.9000015258789 Par conséquent, ce test unitaire échoue:

 float f = -99.9f; double d = (double)f; Assert.AreEqual(-99.9, d); 

Je comprends qu’un supplément de 32 bits est ajouté à divers endroits. Pourtant, la valeur que je recherche, -99.9000000000000 , est bien représentée comme un double si je l’assigne directement, comme en témoigne ce test unitaire:

 double d2 = -99.9; Assert.AreEqual(-99.9, d2); Assert.AreEqual(-99.9000000000000, d2); 

Ma dernière question est donc la suivante: est-il possible de prendre -99.9f et de le convertir en un double tel qu’il sera vraiment égal à -99.9000000000000 ?

MODIFIER

J’ai trouvé une solution de contournement qui, pour le moment, semble bien fonctionner pour mon application particulière (c’est-à-dire une solution pour laquelle je ne sais pas à l’avance combien de chiffres de précision pour arrondir le flotteur):

 float f = -99.9f; double d = double.Parse(f.ToSsortingng("r")); Assert.AreEqual(-99.9, d); 

Je ne sais pas si cela fonctionnerait dans tous les cas. les commentaires sont les bienvenus

EDIT 2 Selon la réponse de Lasse V. Karlsen ci-dessous, la solution consistait à utiliser l’équivalent MSTest de la méthode AreEqual:

 Assert.AreEqual(-99.9, d, 0.00001); 

Notez que si je devais append un zéro de plus à cette valeur delta, le test échouerait

Le problème est que 99.9 ne peut pas être représenté exactement en Single ou en Double (correspond aux mots clés C # float et double ).

Le problème ici est le .9 qui doit être écrit comme une sum de fractions de pouvoirs de deux. Comme la représentation sous-jacente est constituée de bits, nous ne pouvons dire que pour chaque position de bit qu’elle est activée ou désactivée, et chaque position de bit du côté de la fraction signifie 1/2 ^ N, ce qui signifie que la valeur 0,9 peut être représentée ainsi:

 1 1 1 1 1 1 1 1 1 1 - + - + - + -- + --- + ---- + ---- + ----- + ----- + ------ + .... 2 4 8 64 128 1024 2048 16384 32768 262144 

En fin de compte, vous finirez par être légèrement inférieur à 0,9 ou légèrement supérieur à 0,9, quel que soit le nombre de bits dont vous disposez, car 0,9 ne peut pas être représenté exactement par des bits comme celui-ci, mais c’est ainsi que sont stockés les nombres à virgule flottante.

La représentation à l’écran que vous voyez, soit en appelant .ToSsortingng() , ou simplement Console.WriteLine , ou "" + f , ou même en inspectant le débogueur, peut être arrondie, ce qui signifie que vous ne verrez pas nombre exact qui est stocké, uniquement comment l’arrondi / le formatage l’a renvoyé.

C’est pourquoi, lorsque vous comparez des valeurs à virgule flottante, vous devez toujours utiliser une comparaison “epsilon”.

Votre test unitaire indique en gros “Tant que la valeur réelle est exactement la même que la valeur attendue, tout va bien”, mais cela est rarement le cas pour les valeurs à virgule flottante. En fait, je dirais que cela arriverait si rarement que vous le découvririez immédiatement, sauf pour une chose, lorsque vous utilisez des constantes à la fois attendues et réelles et qu’elles sont du même type.

Vous devez plutôt écrire votre code pour vérifier que la valeur réelle est suffisamment proche de la valeur attendue, où “assez proche” est très petit par rapport à la plage de valeurs possibles et sera probablement différent pour chaque cas.

Vous ne devez pas vous attendre à ce que Single et Double puissent représenter exactement les mêmes valeurs. Par conséquent, des conversions en arrière peuvent même renvoyer des valeurs différentes.

Sachez également qu’avec .NET, les calculs en virgule flottante ont un comportement légèrement différent des constructions DEBUG et RELEASE, car la partie FPU intégrée du processeur peut généralement fonctionner avec une précision supérieure à celle des types de stockage. Cela signifie que si le compilateur / optimiseur / jitter finit par utiliser le FPU pour une séquence de calculs, le résultat peut être légèrement différent de celui où les valeurs sont stockées temporairement dans des variables générées par le compilateur.

Voici comment vous écrivez une comparaison:

 if (Math.Abs(actual - expected) < 0.000001) { ... } 

0.000001 est "assez proche".

En termes de nombreux frameworks de tests unitaires, tels que NUnit, vous pouvez demander à la comparaison de prendre cela en compte comme ceci:

 Assert.AreEqual(-99.9, d2, 0.000001); 

En outre, si vous souhaitez en savoir plus à ce sujet, cet article contient de nombreux détails sanglants:

  • Ce que tout informaticien devrait savoir sur l’arithmétique en virgule flottante

Le problème que vous voyez est dû à la résolution flottante.

Les flotteurs ont une faible précision (en fonction de vos besoins) et même si vous affectez -99.9f, le flottant n’aura pas cette valeur, il aura vraiment -99.9000015258789f comme valeur, car si vous le convertissez en double, vous voyez cette valeur. , donc le problème ne convertit pas en double, utilise un float.

Si vous savez combien de décimales auront votre nombre, vous pouvez utiliser Math.Round (floatNumber, decimalPlaces) et cela résoudra presque tous les problèmes, mais pour certaines valeurs, cela échouera également en fonction du nombre de décimales que vous avez.

À votre santé.