Trouver les min / max d’un float / double ayant la même représentation interne

Rafraîchir sur les points flottants (également PDF ), IEEE-754 et participer à cette discussion sur l’arrondissement des virgules flottantes lors de la conversion en chaînes m’a amené à bricoler: comment puis-je obtenir la valeur maximale et minimale pour un nombre à virgule flottante donné dont les représentations binarys sont égaux.

Avertissement : pour cette discussion, j’aime bien restr en virgule flottante 32 bits et 64 bits comme décrit par IEEE-754. Les points en virgule flottante étendue (80 bits), les quads (128 bits IEEE-754-2008) ou toute autre norme (IEEE-854) ne m’intéressent pas.

Contexte : Les ordinateurs ne représentent pas 0.1 en représentation binary. En C #, un float représente cela en tant que 3DCCCCCD interne (C # utilise un arrondi au plus proche) et un double en tant que 3FB999999999999A . Les mêmes modèles de bits sont utilisés pour les décimales 0.100000005 (float) et 0.1000000000000000124 (double), mais pas pour 0.1000000000000000144 (double).

Pour plus de commodité, le code C # suivant donne ces représentations internes:

 ssortingng GetHex(float f) { return BitConverter.ToUInt32(BitConverter.GetBytes(f), 0).ToSsortingng("X"); } ssortingng GetHex(double d) { return BitConverter.ToUInt64(BitConverter.GetBytes(d), 0).ToSsortingng("X"); } // float Console.WriteLine(GetHex(0.1F)); // double Console.WriteLine(GetHex(0.1)); 

Dans le cas de 0.1 , il n’y a pas de nombre décimal inférieur représenté avec le même motif binary, les 3F7FFFFF 0.99...99 donneront une représentation binary différente (c’est-à-dire que float pour 3F7FFFFF donne 3F7FFFFF interne).

Ma question est simple: comment puis-je trouver la valeur décimale la plus basse et la plus haute pour un flottant donné (ou double) qui est stocké en interne dans la même représentation binary.

Pourquoi : (je sais que vous le demanderez) pour rechercher l’erreur d’arrondi dans .NET lors de la conversion en chaîne et lors de la conversion à partir d’une chaîne, pour rechercher la valeur exacte interne et pour mieux comprendre mes propres erreurs d’arrondi.

Je suppose que cela ressemble à quelque chose du genre: prenez la mantisse, enlevez le rest, obtenez sa valeur exacte, augmentez-en une (mantisse-bit) plus haut, et calculez la moyenne: tout ce qui se trouve en dessous produira le même motif binary. Mon problème principal est: comment obtenir la partie fractionnaire sous forme d’entier (la manipulation de bits n’est pas mon atout le plus puissant). La classe DoubleConverter de Jon Skeet peut être utile.

Une façon de répondre à votre question consiste à déterminer la taille d’un nombre ULP ou d’un nombre dans le dernier chiffre de votre nombre à virgule flottante. En simplifiant un peu, il s’agit de la distance entre un nombre à virgule flottante donné et le nombre immédiatement supérieur. Encore une fois, pour simplifier un peu, étant donné une valeur en virgule flottante représentable x, toute chaîne décimale dont la valeur est comprise entre (x – 1/2 ulp) et (x + 1/2 ulp) sera arrondie à x lorsqu’elle sera convertie en un nombre flottant. -point valeur.

Le truc, c’est que (x +/- 1/2 ulp) n’est pas un nombre à virgule flottante représentable. Par conséquent, pour calculer sa valeur, vous devez utiliser un type à virgule flottante plus large (s’il en existe un) ou une largeur arbitraire de grande décimale. ou un type similaire pour faire le calcul.

Comment trouvez-vous la taille d’un ulp? Un moyen relativement simple est à peu près ce que vous avez suggéré, écrit ici est le pseudocode C-ish parce que je ne connais pas C #:

 float absX = absoluteValue(x); uint32_t bitPattern = getRepresentationOfFloat(absx); bitPattern++; float nextFloatNumber = getFloatFromRepresentation(bitPattern); float ulpOfX = (nextFloatNumber - absX); 

Cela fonctionne parce que l’ajout d’un à la configuration de bits de x correspond exactement à l’ajout d’un ulp à la valeur de x. Il n’ya pas d’arrondi en virgule flottante dans la soustraction car les valeurs en cause sont si proches (en particulier, il existe un théorème de l’arithmétique en virgule flottante ieee-754 selon laquelle si deux nombres x et y vérifient y / 2 <= x <= 2y, alors x – y est calculé exactement). Les seules mises en garde ici sont:

  1. si x est le plus grand nombre fini, ceci ne fonctionnera pas (il retournera inf , ce qui est clairement faux).
  2. si votre plate-forme ne prend pas correctement en charge le dépassement progressif (par exemple, un périphérique intégré fonctionnant en mode de mise à zéro), cela ne fonctionnera pas pour les très petites valeurs de x.

Il semble que vous ne soyez dans aucune de ces situations. Cela devrait donc vous convenir.

Maintenant que vous savez ce qu’est un ulp de x, vous pouvez trouver l’intervalle de valeurs arrondi à x. Vous pouvez calculer ulp (x) / 2 avec exactitude en virgule flottante, car la division par 2 en virgule flottante est exacte (encore une fois, à moins que le dépassement ne soit insuffisant). Il suffit ensuite de calculer la valeur de x +/- ulp (x) / 2 de type virgule flottante plus grand convenable ( double fonctionnera si vous êtes intéressé par float ) ou de type Big Decimal, et vous avez votre intervalle.

J’ai fait quelques hypothèses simplificasortingces à travers cette explication. Si vous avez vraiment besoin que cela soit énoncé avec précision, laissez un commentaire et je développerai les sections un peu floues quand j’en aurai l’occasion.


Une autre note la déclaration suivante dans votre question:

Dans le cas de 0.1, il n’y a pas de nombre décimal inférieur représenté avec le même motif binary

est incorrect. Vous venez de regarder les mauvaises valeurs (0.999999 … au lieu de 0.099999 … – une faute de frappe facile à créer).

Python 3.1 vient d’implémenter quelque chose comme ceci: voir le journal des modifications (défilement un peu plus bas) , rapport de bogue .