Un charmant problème de conversion

Le
rixed
Petit exercice amusant :
comprendre pourquoi ce programme n'affiche pas deux fois la même valeur.

#include <stdio.h>
int main(void)
{
unsigned int a = 3232235530U;
float value = a;
printf("a=%.2f %.2f", (float)a, value);
return 0;
}


Ca nous a bien occupé une demi heure aujourd'hui, j'espère que ça vous
amusera autant que nous.
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses Page 1 / 3
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
JKB
Le #20119671
Le 11-09-2009, ? propos de
Un charmant problème de conversion,
?crivait dans fr.comp.lang.c :
Petit exercice amusant :
comprendre pourquoi ce programme n'affiche pas deux fois la même valeur.

#include int main(void)
{
unsigned int a = 3232235530U;
float value = a;
printf("a=%.2f %.2fn", (float)a, value);
return 0;
}


Ca nous a bien occupé une demi heure aujourd'hui, j'espère que ça vous
amusera autant que nous.



Ne n'ai pas vu pourquoi en regardant le code en question. Je compile
bêtement (GNV sous VMS, c'est tout ce que j'ai présentement sous la
main) et...

$ run a.out
a232235520.00 3232235520.00
$

Alors, spécificité de VMS ou alors j'ai raté quelque chose ?

JKB

--
Le cerveau, c'est un véritable scandale écologique. Il représente 2% de notre
masse corporelle, mais disperse à lui seul 25% de l'énergie que nous
consommons tous les jours.
Kojak
Le #20119771
Le Fri, 11 Sep 2009 15:14:49 +0000 (UTC),
JKB a écrit :

$ run a.out
a232235520.00 3232235520.00
$

Alors, spécificité de VMS ou alors j'ai raté quelque chos e ?



Il compile en 32 bits avec les erreurs d'arrondis associées.
En 64 bits, pas de problème.

--
Jacques.
Stephane Legras-Decussy
Le #20122521

unsigned int a = 3232235530U;



pour moi c'est interdit de faire ça, ce nombre
ne rentre pas dans la plage garantie
de 0 à 65 535 ...
candide
Le #20123351
a écrit :
Petit exercice amusant :
comprendre pourquoi ce programme n'affiche pas deux fois la même valeur.

#include int main(void)
{
unsigned int a = 3232235530U;
float value = a;
printf("a=%.2f %.2fn", (float)a, value);
return 0;
}


Ca nous a bien occupé une demi heure aujourd'hui, j'espère que ça vous
amusera autant que nous.




Très amusant en effet. Chez moi, changer float en double ne produit plus
l'anomalie constatée. Le fait que a soit non signé n'a pas de rôle. J'ai essayé
de trouver une valeur minimale pour a et qui affiche des valeurs différentes et
j'ai trouvé 16777217 qui est, comme par hasard 2**24+1.


#include
int main(void)
{
int a777225;
float u=a;

printf("%f %fn", u, (double) a);
return 0;
}

qui affiche

$ gcc -stdÉ9 -o x fclc.c
$ ./x
16777216.000000 16777217.000000



Mais pour 2**24+2, pas de problème. Il s'agit sans doute d'un problème de
représentation exacte d'un entier en flottant mais je ne sais en dire plus.
Est-ce que ce serait en rapport avec cet extrait de la Norme


6.3.1.3 Signed and unsigned integers
(...)
When a value of integer type is converted to a real floating type, if the value
being converted can be represented exactly in the new type, it is unchanged. If
the value being converted is in the range of values that can be represented but
cannot be represented exactly, the result is either the nearest higher or
nearest lower representable value, chosen in an implementation-defined manner.

?

Merci de nous transmettre vos conclusions ;)
candide
Le #20123341
Stephane Legras-Decussy a écrit :

unsigned int a = 3232235530U;



pour moi c'est interdit de faire ça, ce nombre
ne rentre pas dans la plage garantie
de 0 à 65 535 ...




Tu n'as pas d'overflow avec des unsigned. Et de toute façon je crois que le
problème posé par le PO resterait le même avec un type unsigned long int.
Pierre Maurette
Le #20123621
candide, le 12/09/2009 a écrit :
a écrit :
Petit exercice amusant :
comprendre pourquoi ce programme n'affiche pas deux fois la même valeur.

#include int main(void)
{
unsigned int a = 3232235530U;
float value = a;
printf("a=%.2f %.2fn", (float)a, value);
return 0;
}


Ca nous a bien occupé une demi heure aujourd'hui, j'espère que ça vous
amusera autant que nous.




Très amusant en effet. Chez moi, changer float en double ne produit plus
l'anomalie constatée. Le fait que a soit non signé n'a pas de rôle. J'ai
essayé de trouver une valeur minimale pour a et qui affiche des valeurs
différentes et j'ai trouvé 16777217 qui est, comme par hasard 2**24+1.


#include
int main(void)
{
int a777225;
float u=a;

printf("%f %fn", u, (double) a);
return 0;
}

qui affiche

$ gcc -stdÉ9 -o x fclc.c
$ ./x
16777216.000000 16777217.000000



Mais pour 2**24+2, pas de problème. Il s'agit sans doute d'un problème de
représentation exacte d'un entier en flottant mais je ne sais en dire plus.
Est-ce que ce serait en rapport avec cet extrait de la Norme



Pas vraiment la norme(*) mais le fait que le PO et tout le monde ici a
des float conforme - au moins en taille - à IEEE 754 32bit, qui est
aussi le type 32 bits de la FPU Intel. Et particulièrement une mantisse
de taille 23 bits.
Donc logiquement on devrait commencer à perdre de la précision à partir
de la moitié de ce que vous avez trouvé, 2**23 + 1 soit 8388607.
L'explication est que si la mantisse occupe 23 bits, la précision est
de 24 bits. Tenant compte du fait qu'il y a plusieurs combinaisons
mantisse - exposant donnant la même valeur (1.23 e+10 et 0.123 e100 par
exemple), on normalise cette représentation en considérant que le 1
précédent le . est systématique et implicite. Un exemple en binaire,
sur une hypothétique autant qu'inutile FPU à flottant 8 bits, disposant
de 4 bits pour la mantisse, 3 pour l'exposant, et 1 bit de signe qu'on
ignore, nous ne raisonnons que sur les positifs(**). Nous prétendons
pouvoir représenter de façon exacte les entiers entre -31 et 31, et non
-15 à 15. On fait un essai avec 29, 11101. On va coder 1101 dans la
mantisse de 4 bits, nous aurons donc avec le 1. implicite un 1.1101. Il
faudra l'élever à la puissance 4, soit 100 en binaire. Sa
représentation signe - mantisse - exposant sera donc: [0 1101 100].
Mais alors quid de la représentation de 13, 1101 ? Il va tout
simplement se coder avec 1010 dans la mantisse. Donc avec le 1.
implicite 1.1010. Qu'il faudra élever à la puissance 3, 11 en binaire,
pour retomber sur nos pattes. La représentation sera donc de [0 1010
11]. Voilà en très simple(***). On peut jouer avec printf() et le
spécificateur de conversion a (ou A), c'est assez visuel, voir la
norme.
Un fois admise cette précision de 24 bits, qui sera confirmée par la
consultation de FLT_RADIX et FLT_MANT_DIG, le comportement est tout à
fait prévisible. A partir de 2**24 et jusqu'à 2**25 - 1, on va perdre
le dernier bit. S'il est à 1, l'affichage du programme de l'OP fera
apparaître la différence. S'il est à 0, donc tout les nombres pairs,
rien ne se verra. A partir de 2**25, 2 bits seront concernés, donc
seuls les multiples de 4 seront indemnes. Et ainsi de suite.

(*) La norme n'impose pas grand chose, après une lecture en diagonale
très rapide de §5.2.4.2.2. Pour ce qui est de la mantisse, il est
imposé en float une représentation "sûre" des nombres de 6 chiffres en
décimal. Ce qui imposerait sauf erreur de ma part 20 digits (bits) si
la base est 2. Et il doit exister un type qui représente de façon
"sûre" des nombres de 10 chiffres en décimal. D'où une mantisse de 32
bits si base 2.

(**) Contrairement aux entiers, les positifs et les négatifs sont très
symétriques pour les flottants.

(***) Bien entendu la réalité est plus complexe, et il faut bien voir
que ce bit qu'on gagne là, on le perdra ailleurs. En réalité un format
de flottant, et partant une FPU, est un truc imparfait, dangereux, mais
pragmatique et utile. Un règle de base de son utilisation est de tout
tenter pour l'éviter.

6.3.1.3 Signed and unsigned integers
(...)
When a value of integer type is converted to a real floating type, if the
value being converted can be represented exactly in the new type, it is
unchanged. If the value being converted is in the range of values that can be
represented but cannot be represented exactly, the result is either the
nearest higher or nearest lower representable value, chosen in an
implementation-defined manner.



Dans l'exemple de l'OP, le comportement de value est sans ambiguité.
value est une variable float, la perte de précision se fait lors de son
initialisation est l'affichage est sans surprise.

Pour ce qui est du (float)a, de toute évidence a est converti en double
et affiché comme un double. D'ailleurs, selon la norme, printf() ne
sait afficher que des double. Le cast (float) est donc ignoré, il n'y a
pas de temporaire float. Je ne saurais dire avec certitude si ce
comportement est prévisible, il me semble qu'il y a régulièrement des
posts à ce sujet ici même. Disons qu'il ne m'étonne pas et que
l'affichage des flottants dans un programme au moins partiellement
portable est un truc qui me gonfle particulièrement. J'aurais tendance
à penser que le comportement peut changer d'un compilateur à l'autre,
et même - le cas est courant et bien chiant - d'une bibliothèque
dynamique à l'autre.
En bref, si on veut afficher le float, on fait un float avant le
printf() "et pi cé tou".


--
Pierre Maurette
Richard Delorme
Le #20123741
Le 11/09/2009 22:53, Stephane Legras-Decussy a écrit :

unsigned int a = 3232235530U;



pour moi c'est interdit de faire ça, ce nombre
ne rentre pas dans la plage garantie
de 0 à 65 535 ...



En quoi c'est interdit ?
Si l'on doit se conformer strictement aux valeurs minimales de la norme
on ne programme plus grand chose.

--
Richard
-ed-
Le #20124251
On 11 sep, 17:09, wrote:
Petit exercice amusant :
comprendre pourquoi ce programme n'affiche pas deux fois la même valeur .

#include int main(void)
{
    unsigned int a = 3232235530U;
    float value = a;
    printf("a=%.2f %.2fn", (float)a, value);
    return 0;

}

Ca nous a bien occupé une demi heure aujourd'hui, j'espère que ça v ous
amusera autant que nous.



Bah, moi, j'obtiens

a232235530.00 3232235530.00

Process returned 0 (0x0) execution time : 0.046 s
Press any key to continue.

parce que mes int font 32 bit... Pour être portable C90 et éviter les
conversions douteuses et inutiles :

#include
int main (void)
{
unsigned long a = 3232235530UL;
double value = a;
printf ("a=%.2f %.2fn", (double) a, value);
return 0;
}
-ed-
Le #20124331
On 12 sep, 02:18, candide
Stephane Legras-Decussy a écrit :

> >
>>    unsigned int a = 3232235530U;

> pour moi c'est interdit de faire ça, ce nombre
> ne rentre pas dans la plage garantie
> de 0 à 65 535 ...

Tu n'as pas d'overflow avec des unsigned. Et de toute façon je crois qu e le
problème posé par le PO resterait le même avec un type unsigned lon g int.



Y'a peut être pas d'overflow, mais si l'int fait 16-bit (oui, ça
existe, ne serait-ce que pendant la phase de démarrage de ton PC...),
et bien ça ne rentre pas. Point. Le comportement est donc différent de
celui que tu attends.
-ed-
Le #20124321
On 12 sep, 07:14, Richard Delorme
Le 11/09/2009 22:53, Stephane Legras-Decussy a écrit :

> >
>>     unsigned int a = 3232235530U;

> pour moi c'est interdit de faire ça, ce nombre
> ne rentre pas dans la plage garantie
> de 0 à 65 535 ...

En quoi c'est interdit ?



C'est interdit si on cherche à écrire du code portable. C'est autoris é
si on impose un choix de plateformes avec des int à 32-bit...

Si l'on doit se conformer strictement aux valeurs minimales de la norme
on ne programme plus grand chose.



Réponse idiote. On choisit le bon type, c'est tout. (ici, unsigned
long).
Publicité
Poster une réponse
Anonyme