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.