Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Un charmant problème de conversion

23 réponses
Avatar
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\n", (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.

10 réponses

1 2 3
Avatar
Richard Delorme
Le 12/09/2009 09:52, -ed- a écrit :
On 12 sep, 07:14, Richard Delorme wrote:
Le 11/09/2009 22:53, Stephane Legras-Decussy a écrit :

a écrit dans le message de news:

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.



Ce n'est donc pas interdit car il n'est pas interdit d'écrire du code
non portable.

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).



Réponse idiote. Chez moi un unsigned long occupe 64 bits. Si j'ai besoin
d'entier 32 bits, et pas plus, unsigned long n'est pas le bon type.

--
Richard
Avatar
Richard Delorme
Le 12/09/2009 09:46, -ed- a écrit :
On 11 sep, 17:09, wrote:
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 %.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.



Bah, moi, j'obtiens

a232235530.00 3232235530.00



Bizarre...

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

parce que mes int font 32 bit...



Et tes float ?
Parce que mes ints font aussi 32 bits, mais j'obtiens :
a232235530.00 3232235520.00
ou
a232235520.00 3232235520.00
selon les options de compilations.

Pour être portable C90 et éviter les
conversions douteuses et inutiles :

#include<stdio.h>

int main (void)
{
unsigned long a = 3232235530UL;
double value = a;
printf ("a=%.2f %.2fn", (double) a, value);
return 0;
}



Ça ne répond pas à la question. Voir le poste de Pierre Maurette pour la
réponse.

--
Richard
Avatar
Pierre Maurette
-ed-, le 12/09/2009 a écrit :
On 12 sep, 07:14, Richard Delorme wrote:
Le 11/09/2009 22:53, Stephane Legras-Decussy a écrit :

 a écrit dans le message de news:

    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).



Votre extrême rigueur vous conduit à vous contenter de la 2**48 ème
partie des représentations possibles sur beaucoup d'implémentations
actuelles, et de la 2**16 ème partie sur l'immense majorité des
implémentations. A quoi ça sert que les fondeurs ils se décarcassent ?

Je ne comprends pas votre approche quasi religieuse. Vous vous situez
systématiquement dans un contexte de code portable directement, sans
intervention du préprocesseur. Le seul avantage que j'y vois c'est que
ça facilite les réponses faites aux divers intervenants. On peut
presque coder un truc pour les formuler - pas en C, quand même ;-) Je
manque d'expérience de la production réelle, mais je ne peux pas croire
que vous puissiez travailler sur de telles bases. A part
l'enseignement, mais alors...

Quand à vos préconisations de type, je les trouve étranges. Vous
semblez ignorer stdint.h . C'est C99 mais largement disponible sur
d'autres implémentations, dont MSVC. Et de toutes façons, on peut s'en
inspirer, plutôt que de prendre un unsigned long au petit bonheur la
chance.

Je vous en prie, ne prenez pas mal ce que j'écris. Je peux me tromper,
votre approche est souvent salutaire. Vous m'avez beaucoup appris,
parfois bien fait rire ce qui ne gâte rien. MAis je trouve sincèrement
que vous allez trop loin.

Bonne fin de week-end...

--
Pierre Maurette
Avatar
candide
Pierre Maurette a écrit :
candide, le 12/09/2009 a écrit :



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.




OK, ceci explique la valeur limite et la fait effectivement que la parité du
nombre joue un rôle, mais je ne vois pas en quoi ça répond au problème initial,
celui des valeurs différentes alors que les conversions sont identiques.

Dans le code :

unsigned long int a = 3232235530UL; /* long : pour éviter les rques HS */
float value = a;

value reçoit 3232235530UL converti en float :

------------------
In simple assignment (=), the value of the right operand is converted to the
type of the assignment expression and replaces the value stored in the object
designated by the left operand.
------------------

et je ne vois pas pourquoi cette valeur diffèrerait de celle que donne le cast
(float)a :

------------------
Preceding an expression by a parenthesized type name converts the value of the
expression to the named type. This construction is called a cast.
------------------









(*) 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".




Avatar
candide
-ed- a écrit :


Bah, moi, j'obtiens

a232235530.00 3232235530.00

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




Le temps d'exécution importe moins que les options de compilation.


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

#include <stdio.h>

int main (void)
{
unsigned long a = 3232235530UL;
double value = a;
printf ("a=%.2f %.2fn", (double) a, value);
return 0;
}




Réponse shadock. Le problème ne se pose pas si value est de type double au lieu
de float.
Avatar
Stephane Legras-Decussy
"Pierre Maurette" a écrit dans le message de
news:
Je ne comprends pas votre approche quasi religieuse. Vous vous situez
systématiquement dans un contexte de code portable directement, sans
intervention du préprocesseur.



pour moi l'approche "religieuse" et l'absence de préprocesseur
ça permet de libérer l'esprit et de simplifier... ça me
laisse temps et énergie pour réfléchir aux trucs
importants qui ne vont pas se faire religieusement...
Avatar
rixed
Effectivement le résultat dépend du compilateur et/ou de l'architecture.
Je ne m'attendais pas à ça.

Je donne donc plus de détails :

Le comportement que j'attend du code donné, c'est qu'il affiche :

a232235520.00 3232235520.00

C'est à dire que le cast en float, pour les raisons exposées par Pierre,
ne donne _pas_ le bon résultat.

Nous étions donc surpris de tomber là dessus :

a232235530.00 3232235520.00

Et la question était donc : mais pourquoi donc le cast dans le printf
donne t-il le bon résultat ?

Ce résultat n'est donc pas universel. Il a été obtenu notament sur x86
avec gcc 4.3.2.

Sur mips + gcc-4.3.1 ou gcc.4.4.0 c'est le résultat attendu (celui qui
est "faux" deux fois) qui est obtenu. De même sur arm + gcc 3.4.6

Et ceci d'ailleurs quelque soient les options de compilation essayées.

Je n'ai fait que grepper dans un draft de la norme, mais selon moi les
extraits suivants :


6.3.1.4 Real floating and integer

[#2] When a value of integer type is converted to a real |
floating type, 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 value, chosen in an implementation-defined manner. (...)

6.3.1.5 Real floating types

[#1] When a float is promoted to double or long double, or a
double is promoted to long double, its value is unchanged.

Stipulent que (double)(float)3232235530U devrait valoir
3232235520.00. Or dans notre cas il semble que gcc ait préféré,
étant donné le cast implicite en double, ignorer notre cast explicite
en float.

A y repenser, je pense qu'il n'en a pas le droit. Surtout à la lueur
de :

5.1.2.3 [#13] (...) an
explicit store and load is required to round to the
precision of the storage type. In particular, casts and
assignments are required to perform their specified
conversion. For the fragment

double d1, d2;
float f;
d1 = f = expression;
d2 = (float) expressions;

the values assigned to d1 and d2 are required to have been
converted to float.


Qu'en pensez vous ?
Avatar
Jean-Marc Bourguet
writes:

Qu'en pensez vous ?



http://gcc.gnu.org/bugzilla/show_bug.cgi?id23

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
candide
a écrit :
Effectivement le résultat dépend du compilateur et/ou de l'architecture.
Je ne m'attendais pas à ça.



Certaines conversions sont implementation defined, en particulier type entier ->
type flottant, cf. l'extrait de la norme que tu as rappelé.



Et la question était donc : mais pourquoi donc le cast dans le printf
donne t-il le bon résultat ?




Pour moi ce n'est pas la bonne question.

Stipulent que (double)(float)3232235530U devrait valoir
3232235520.00. Or dans notre cas il semble que gcc ait préféré,
étant donné le cast implicite en double, ignorer notre cast explicite
en float.




La conversion en double due à la promotion par défaut n'est pas discriminante :

le code suivant ne fait aucune conversion en double

#include <stdio.h>

int main(void)
{
unsigned int a = 3232235530U;
float value = a;

if ((float) a - value > 5.0)
printf("ecartn");
return 0;
}


et pourtant il témoigne (chez moi) de la présence d'un écart de valeurs :

$ gcc -W -Wall -stdÉ9 -pedantic -o x fclc.c
$ ./x
ecart
$


Je ne comprends pas la présence de cette écart puisque (float)a et
float value = a; devraient conduire aux mêmes valeurs, cf. les extraits de la
Norme dans ma réponse au message de Pierre Maurette.
Avatar
candide
candide a écrit :

le code suivant ne fait aucune conversion en double



(...)

if ((float) a - value > 5.0)




Si, il en fait puisqu'une constante flottante non suffixée est de type double
donc il faut écrire

if ((float) a - value > 5.0F)

mais ça ne change rien à la présence d'un écart.
1 2 3