Expliquez ce code dans K & R 2-1

J’essaie de déterminer l’étendue des divers types à virgule flottante. Quand j’ai lu ce code:

#include  main() { float fl, fltest, last; double dbl, dbltest, dblast; fl = 0.0; fltest = 0.0; while (fl == 0.0) { last = fltest; fltest = fltest + 1111e28; fl = (fl + fltest) - fltest; } printf("Maximum range of float variable: %e\n", last); dbl = 0.0; dbltest = 0.0; while (dbl == 0.0) { dblast = dbltest; dbltest = dbltest + 1111e297; dbl = (dbl + dbltest) - dbltest; } printf("Maximum range of double variable: %e\n", dblast); return 0; } 

Je ne comprends pas pourquoi l’auteur a ajouté 1111e28 à la variable fltest ?

La boucle se termine lorsque fltest atteint +Inf , car à ce point fl = (fl + fltest) - fltest devient NaN , ce qui n’est pas fl = (fl + fltest) - fltest à 0.0 . last contient une valeur qui, ajoutée à 1111e28 produit +Inf et est donc proche de la limite supérieure de float .

1111e28 est choisi pour atteindre +Inf raisonnablement rapidement; elle doit également être suffisamment grande pour que, si elle est ajoutée à des valeurs élevées, la boucle continue à progresser, c’est-à-dire au moins égale à l’écart entre les valeurs float non infinies les plus importantes et les plus importantes.

OP: … pourquoi l’auteur a ajouté 1111e28 à la variable fltest ?
R: [Edit] Pour que le code fonctionne avec float , 1111e28 ou 1.111e31 cette valeur delta doit être soigneusement sélectionnée. Il devrait être suffisamment grand pour que si fltest soit FLT_MAX , la sum de fltest + delta déborde et devient float.infinity . En mode arrondi au plus proche, il s’agit de FLT_MAX*FLT_EPSILON/4 . Sur ma machine:

 min_delta 1.014120601e+31 1/2 step between 2nd largest and FLT_MAX FLT_MAX 3.402823466e+38 FLT_EPSILON 8.388608000e+06 FLT_MAX*FLT_EPSILON 4.056481679e+31 

delta doit être suffisamment petit pour que si f1test soit le deuxième plus grand nombre, en ajoutant delta, la sum ne correspond pas à float.infinity et FLT_MAX . C’est 3x min_delta

 max_delta 3.042361441e+31 

Donc, 1.014120601e+31 <= 1111e28 < 3.042361441e+31 .

@ david.pfx Oui. 1111e28 est un nombre mignon et il est dans la gamme.

Remarque: Des complications surviennent lorsque le calcul et ses valeurs intermédiaires, même si les variables sont float peuvent être calculés plus tôt que double . Ceci est autorisé en C et contrôlé par FLT_EVAL_METHOD ou un codage très prudent.


1111e28 est une valeur curieuse qui a du sens si l'auteur connaissait déjà la plage générale de FLT_MAX .

Le code ci-dessous est censé faire plusieurs boucles (24946069 sur une plate-forme de test). Espérons que la valeur fltest devienne "infinie". Alors f1 deviendra NaN en tant que différence d'Infinity - Infinity. La boucle while se termine par Nan! = 0.0. @ecatmur

 while (fl == 0.0) { last = fltest; fltest = fltest + 1111e28; fl = (fl + fltest) - fltest; } 

La mise en boucle, si elle est effectuée par incréments suffisamment petits, donnera une réponse précise. FLT_EPSILON est nécessaire pour connaître FLT_MAX et FLT_EPSILON .

Le problème avec ceci est que C ne définit pas les plages FLT_MAX et DBL_MAX autres que celles-ci doivent être au moins 1E+37 . Donc, si la valeur maximale était assez grande, la valeur d'incrément de 1111e28 ou 1111e297 n'aurait aucun effet. Exemple: dbltest = dbltest + 1111e297; , pour dbltest = 1e400 certainement pas de 1e400 à moins de dbltest cent chiffres décimaux de précision.

Si DBL_MAX était inférieur à 1111e297, la méthode échouait également. Remarque: sur les plates-formes simples en 2014, il n'est pas surprenant de trouver double et float comme étant le même binary IEEE 4 octets32 ) La première fois que la boucle, dbltest devient infini et la boucle s'arrête, indiquant "Plage maximale de la variable double: 0.000000e + 00 ".

Il existe de nombreuses façons de calculer efficacement la valeur maximale du flottant. Voici un exemple qui utilise une valeur initiale aléatoire pour montrer sa résilience à la variante potentielle FLT_MAX .

 float float_max(void) { float nextx = 1.0 + rand()/RAND_MAX; float x; do { x = nextx; nextx *= 2; } while (!isinf(nextx)); float delta = x; do { nextx = x + delta/2; if (!isinf(nextx)) { x = nextx; } delta /= 2; } while (delta >= 1.0); return x; } 

isinf() est une nouvelle fonction C Assez simple pour rouler le vôtre si nécessaire.

En re: commentaire @didierc

[Modifier]
La précision d'un float et double est implicite avec "epsilon": "la différence entre 1 et la plus petite valeur supérieure à 1 pouvant être représentée dans le type de virgule flottante donné ...". Les valeurs maximales suivent

 FLT_EPSILON 1E-5 DBL_EPSILON 1E-9 

Commentaire de @Pascal Cuoq. "... 1111e28 étant choisi plus grand que FLT_MAX * FLT_EPSILON.", 1111e28 doit être au moins FLT_MAX*FLT_EPSILON pour avoir un impact sur l'ajout de la boucle, mais suffisamment petit pour atteindre avec précision le nombre avant l'infini. Là encore, une connaissance préalable de FLT_MAX et de FLT_EPSILON est nécessaire pour prendre cette décision. Si ces valeurs sont connues à l'avance, le code simple aurait pu être:

 printf("Maximum range of float variable: %e\n", FLT_MAX); 

La plus grande valeur représentable dans un float est 3.40282e + 38. La constante 1111e28 est choisie de telle sorte que l’ajout de cette constante à un nombre compris entre 10 ^ 38 produit toujours une valeur en virgule flottante différente, de sorte que la valeur de fltest continue d’augmenter au fur et à mesure de l’exécution de la fonction. Il doit être suffisamment grand pour restr significatif à 10 ^ 38 et suffisamment petit pour que le résultat soit précis.