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

comparaison de flottants

209 réponses
Avatar
Emmanuel
bonjour tout le monde,

une question de vrai débutant :

Avec le programme essai.c suivant

int main(void) {
return ((1.7 + 0.1) == 1.8) ? 1 : 0;
}

j'obtiens :

$ gcc -Wall -o essai essai.c; ./essai; echo $?
1

Avec le programme essai.c suivant

int main(void) {
float x;
x = 1.7;
return ((x + 0.1) == 1.8) ? 1 : 0;
}

j'obtiens :

$ gcc -Wall -o essai essai.c; ./essai; echo $?
0

Pourtant, le programme

#include <stdio.h>
int main(void) {
float x;
x = 1.7;
printf("%f et %f\n", 1.7 + 0.1, x + 0.1);
}

affiche :
1.800000 et 1.800000

Apparremment, 1.7+0.1 et x+0.1 sont égaux ; j'imagine qu'il s'agit d'un
problème de représentation des flottants en machine mais j'aimerais bien
avoir une confirmation ou une infirmation (et une explication, si possible).

Merci par avance.

Emmanuel

10 réponses

Avatar
Samuel DEVULDER
Vincent Lefevre a écrit :

Moi j'en parle: ton code avec long int n'est pas portable, et



Rahala... D'où sors tu que cet exemple voulait être portable? Du même
chapeau que la machine 64? Comme tous les pinailleurs[*] tu noies le
discours dans des trucs parfaitement inutiles. Dans le cas présent, on
veut voir la différence en mémoire entre deux doubles particuliers
(1.7f+01) et (1.7+0.1) (le printf %f n'est pas suffisant), et pas écrire
du code portable à vocation universelle. Les histoires de long int, de
machines 64, c'est juste pas le sujet.

Bon par contre je veux bien reconnaitre que le sujet d'origine est
épuisé. On a fait le tour de toutes les façon (bonnes ou mauvaises)
d'afficher un double en mémoire sans perturber ce dernier au travers de
conversion/promotion implicites entre float et double.
___
[*] rien à voir avec une infidélité conjugale ;-)

Finalement Misra-C a raison avec le bannissement du type "int" dans le
code. Ca n'est pas suffisant quand le nombre de bit est important.



Mais parfois il ne l'est pas, et la bibliotheque standard utilise
int, etc.



Misra-C ne s'applique pas aux bibliothèques du compilo (qui sont
targetées pour un système précis), mais au code qu'on écrit soit-même et
qui lui doit être portable: il doit tourner sur des ECU 8/16bits tout
comme 32/64, avec byte-order des plus variés... Mais c'est un autre sujet.

sam.
Avatar
Vincent Lefevre
Dans l'article <4bb763aa$0$8296$,
Samuel DEVULDER écrit:

Vincent Lefevre a écrit :

> Moi j'en parle: ton code avec long int n'est pas portable, et

Rahala... D'où sors tu que cet exemple voulait être portable?



Ici, on écrit du C portable. Ou alors on précise l'architecture
en question. En tout cas, l'OP ne l'a pas fait, et ton code n'a
aucune raison de fonctionner sur sa machine.

> Mais parfois il ne l'est pas, et la bibliotheque standard utilise
> int, etc.

Misra-C ne s'applique pas aux bibliothèques du compilo (qui sont
targetées pour un système précis),



N'importe quoi. La bibliothèque standard fait partie de la norme C ISO
et les spec sont indépendantes du système.

mais au code qu'on écrit soit-même et qui lui doit être portable: il
doit tourner sur des ECU 8/16bits tout comme 32/64, avec byte-order
des plus variés... Mais c'est un autre sujet.



On peut utiliser la bibliothèque standard tout en étant portable.
D'ailleurs, c'est fait pour.

--
Vincent Lefèvre - Web: <http://www.vinc17.net/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.net/blog/>
Work: CR INRIA - computer arithmetic / Arénaire project (LIP, ENS-Lyon)
Avatar
Vincent Lefevre
Dans l'article <4bb4ebc1$0$32100$,
Samuel DEVULDER écrit:

Vincent Lefevre a écrit :

>>> Aussi après un appel de rint...
>
>> pour savoir si un nombre est un entier ou pas?
>
> Non, pour convertir en entier parce que dans telle formule, c'est ce
> qu'on a. Si l'erreur était < 0.5, alors on obtient le vrai résultat
> (exact).

Oula.. C'est pas clair. J'arrive pas à comprendre. Je pense que tu as
trop condensé tes idées. De quelle "telle formule" parles tu? Peux tu
détailler ?



Si on sait que tel résultat est un entier et qu'on le calcule avec
une erreur < 0.5, alors si on arrondit le résultat calculé à un
entier, on obtient le résultat exact.

>> Pourquoi donc? tous les doubles sont sérialisés en mémoire de la même
>> façon au format IEEE.
>
> Il y a d'une part les 0 signés: dans la pratique, on veut généralement

Heu.. parles tu encore de "sérialisation" en mémoire là? J'ai
l'impression qu'on s'éloigne du propos.



Oui (sauf que ce n'est pas une sérialisation, juste une représentation
mémoire).

> que +0 et -0 soient considérés comme la même valeur (et d'ailleurs,
> +0.0 == -0.0 en C). D'autre part, si le but est aussi de considérer
> deux NaN comme égaux, l'encodage des NaN n'est pas complètement
> normalisé, cf le payload.

Oui oui sans doute. Mais ici le but est de comparer (1.7f + 0.1) avec
(1.7 + 0.1), pas de -0.0 ou de NaN, Inf et autre dénormalisés là dedans.
Il ne faut pas tomber dans l'ultra spécialisation non plus.



Où est-ce que tu vas chercher ça? Je rappelle que tu répondais à ceci:

http://www.abxsoft.com/misra/misra-test.html
/* Rule 50: Required */
/* Floating point variables shall not be tested for exact equality or */
/* inequality. */
MISRA-C [1998:2004] Function Definition: rule50



Il y a des cas où le test d'égalité a un sens. Mais bon...



Faudrait suivre un peu...

Encore une fois je ne vois pas d'où sort la performance dans le problème
ou l'exercice consistant à examiner en mémoire la différence entre
(1.7f+0.1) et (1.7+0.1). Il ne faut pas inventer des pbs là où il n'y en
a pas.



C'est toi qui invente des trucs. MISRA-C ne se résume pas à
comparer (1.7f+0.1) et (1.7+0.1)!!!

--
Vincent Lefèvre - Web: <http://www.vinc17.net/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.net/blog/>
Work: CR INRIA - computer arithmetic / Arénaire project (LIP, ENS-Lyon)
Avatar
Vincent Lefevre
Dans l'article <4bb4ebc1$0$32100$,
Samuel DEVULDER écrit:

Vincent Lefevre a écrit :

>>> Aussi après un appel de rint...
>
>> pour savoir si un nombre est un entier ou pas?
>
> Non, pour convertir en entier parce que dans telle formule, c'est ce
> qu'on a. Si l'erreur était < 0.5, alors on obtient le vrai résultat
> (exact).

Oula.. C'est pas clair. J'arrive pas à comprendre. Je pense que tu as
trop condensé tes idées. De quelle "telle formule" parles tu? Peux tu
détailler ?



Si on sait que tel résultat est un entier et qu'on le calcule avec
une erreur < 0.5, alors si on arrondit le résultat calculé à un
entier, on obtient le résultat exact.

>> Pourquoi donc? tous les doubles sont sérialisés en mémoire de la même
>> façon au format IEEE.
>
> Il y a d'une part les 0 signés: dans la pratique, on veut généralement

Heu.. parles tu encore de "sérialisation" en mémoire là? J'ai
l'impression qu'on s'éloigne du propos.



Oui (sauf que ce n'est pas une sérialisation, juste une représentation
mémoire).

> que +0 et -0 soient considérés comme la même valeur (et d'ailleurs,
> +0.0 == -0.0 en C). D'autre part, si le but est aussi de considérer
> deux NaN comme égaux, l'encodage des NaN n'est pas complètement
> normalisé, cf le payload.

Oui oui sans doute. Mais ici le but est de comparer (1.7f + 0.1) avec
(1.7 + 0.1), pas de -0.0 ou de NaN, Inf et autre dénormalisés là dedans.
Il ne faut pas tomber dans l'ultra spécialisation non plus.



Où est-ce que tu vas chercher ça? Je rappelle que tu répondais à ceci:

http://www.abxsoft.com/misra/misra-test.html
/* Rule 50: Required */
/* Floating point variables shall not be tested for exact equality or */
/* inequality. */
MISRA-C [1998:2004] Function Definition: rule50



Il y a des cas où le test d'égalité a un sens. Mais bon...



Faudrait suivre un peu...

Encore une fois je ne vois pas d'où sort la performance dans le problème
ou l'exercice consistant à examiner en mémoire la différence entre
(1.7f+0.1) et (1.7+0.1). Il ne faut pas inventer des pbs là où il n'y en
a pas.



C'est toi qui inventes des trucs. MISRA-C ne se résume pas à
comparer (1.7f+0.1) et (1.7+0.1)!!!

--
Vincent Lefèvre - Web: <http://www.vinc17.net/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.net/blog/>
Work: CR INRIA - computer arithmetic / Arénaire project (LIP, ENS-Lyon)
Avatar
Vincent Lefevre
Dans l'article <4bb4db63$0$682$,
Samuel DEVULDER écrit:

Vincent Lefevre a écrit :

> Tout dépend de l'implémentation, qui peut très bien utiliser
> 1 à chaque fois (sauf pour zéro). Je rappelle que l'exposant est
> binaire.

Binaire? genre p-10 veut dire 2^-16?



Non.

la doc que j'ai trouvée
http://www.opengroup.org/onlinepubs/000095399/functions/printf.html
dit exposant décimal (donc 2^-10 = 1/1024 ici).



"Exposant binaire" signifie que cela donne 2^e. Je ne parle pas de
l'écriture de l'exposant, mais de sa sémantique.

J'imagine que 1/0.0 donne +infty, et 1/-0.0 donne sans doute -infty?



Oui.

--
Vincent Lefèvre - Web: <http://www.vinc17.net/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.net/blog/>
Work: CR INRIA - computer arithmetic / Arénaire project (LIP, ENS-Lyon)
Avatar
Vincent Lefevre
Dans l'article ,
Vincent Belaïche écrit:

Sinon mis à part ça j'ai fait un essai de %a avec gcc version 3.4.5, et
ça ne le fait pas.

int main(void)
{
printf("H%a h%a h%a !n",3.14,0.0,12345.0);
printf("%f %f %f n",0x3.23D70A3Dp0, 0x.p0, 0x3039.p0);
return 0;
}

ça fait:

Ha ha ha !
3.140000 0.000000 12345.000000

Ce qui prouve que les constantes sont implantées, mais pas le printf.



Bah gcc n'implémente pas printf. C'est la "bibliothèque C" qui le fait.

--
Vincent Lefèvre - Web: <http://www.vinc17.net/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.net/blog/>
Work: CR INRIA - computer arithmetic / Arénaire project (LIP, ENS-Lyon)
Avatar
Samuel DEVULDER
Vincent Lefevre a écrit :

"Exposant binaire" signifie que cela donne 2^e. Je ne parle pas de
l'écriture de l'exposant, mais de sa sémantique.



Ah ok. Il faut préciser car sur les docs (rares) décrivant %p, toutes
parlent d'exposant décimal et pas binaire. Insister sur l'exposant
*binaire* comme tu l'a fait est très surprenant par rapport à la
littérature. Dans le contexte de "printf" quand on parle décimal,
binaire ou hexa, on parle de la base utilisée pour représenter le
nombre, et dire "exposant binaire" fait penser à une écriture en base
deux de ce dernier. Cela aurait été pour le moins curieux. Par contre
une représentation binaire de la partie fractionnaire aurait été un truc
super intéressant pour le pb de l'OP. Il aurait vu les "000" ajoutés
lors de la conversion de 1.7f en 1.7.

Du reste le "2^exp" est un classique de classique (j'ai des docs TO7 de
~85 qui le présentent ainsi) et ne mérite aucune attention particulière.
Ca n'aurait pas été le cas en decimal64 (définitivement adopté en 2008),
http://speleotrove.com/decimal/decbits.pdf
mais ca n'est vraiment pas le contexte ici.

sam.
Avatar
Samuel DEVULDER
Vincent Lefevre a écrit :
Dans l'article <4bb763aa$0$8296$,
Samuel DEVULDER écrit:

Vincent Lefevre a écrit :



Moi j'en parle: ton code avec long int n'est pas portable, et





Rahala... D'où sors tu que cet exemple voulait être portable?



Ici, on écrit du C portable. Ou alors on précise l'architecture



Non: on répond à la question du poster originel surtout! Comment
afficher la différence entre les deux doubles (je répète plus, c'est
lassant). Certains ont répondus (dont moi), d'autres (dont toi) viennes
pinailler sur des détails, sans même être d'accord les uns avec les
autres. Alors, les curés: au lieu d'imposer votre docte aux autres,
réglez d'abord vos problèmes de sexe des anges entre vous dans un thread
séparé avant de jouer les donneurs de leçon.

Ou alors on précise l'architecture
en question. En tout cas, l'OP ne l'a pas fait, et ton code n'a
aucune raison de fonctionner sur sa machine.



Il en a tout autant de marcher tu sais. Il faut arrêter de supposer à
tord et à travers. Avec des "si" on mettrait Paris en bouteille.

Mais parfois il ne l'est pas, et la bibliotheque standard utilise
int, etc.





Misra-C ne s'applique pas aux bibliothèques du compilo (qui sont
targetées pour un système précis),



N'importe quoi.



Tu n'a pas compris. Je pourrais comprendre que tu fais semblant d'être
bête, mais c'est peut-être autre chose. Je répète: les compilos (et leur
bibliothèques) sont ciblés pour une machine donnée. Misra-c ne regarde
que le code qui est destiné à être portable: le code utilisateur dont
les types *ne doivent pas* utiliser les types "de base". Pour les
bibliothèques, ca n'est pas son problème. D'une part c'est du
précompilé, et d'autre part elles sont toutes préciblées pour une
machine précise.

La bibliothèque standard fait partie de la norme C ISO
et les spec sont indépendantes du système.
mais au code qu'on écrit soit-même et qui lui doit être portable: il
doit tourner sur des ECU 8/16bits tout comme 32/64, avec byte-order
des plus variés... Mais c'est un autre sujet.



On peut utiliser la bibliothèque standard tout en étant portable.
D'ailleurs, c'est fait pour.



Oui quel rapport avec ce que j'ai écrit? Aucun. Il serait temps que
t'ailles te coucher et passer une vraie nuit de sommeil.

Bon w.e. Pascal (Te trompes pas, hein, c'est pas ton prénom, ok?)

sam (c'est une histoire de cloches cette histoires)
Avatar
espie
In article <20100330214159$,
Vincent Lefevre wrote:
Dans l'article <4bafbb44$0$16460$,
Samuel DEVULDER écrit:

Emmanuel a écrit :



> int main(void) {
> float x;
> x = 1.7;
> return ((x + 0.1) == 1.8) ? 1 : 0;
> }
>
> j'obtiens :
>
> $ gcc -Wall -o essai essai.c; ./essai; echo $?
> 0



Attention, le 1.7 de x est un float ici (23bits de précision), et le 0.1
(et le 1.8) de l'addition (ou de la comparaison) est un double (52bits
de précision).



float = 24 bits de précision
double = 53 bits de précision



Meme pas.

(à cause du bit implicite). Le 1.7 est un double, converti en float;
c'est différent de 1.7f (ici, le résultat est le même, mais il peut
y avoir des différences). Dans la pratique, cela peut être encore
plus complexe.



Je pense qu'il convient ici de mentionner que ca peut etre encore plus
la merde, en particulier sur les architectures intel, ou la precision
des nombres stockes en memoire (double sur 8 octets) est differente de la
precisions des registres flottants (80 bits...). Du coup, la plupart des
compilo optimisants font des choses tres perturbantes. Rajouter un petit
bout de calcul quelque part *peut* influencer un calcul proche (hop, une
variable en trop, et hop, un spilled register, qui te fait passer une
valeur de 80 bits a 64 bits). Et bien evidemment, en l'absence de spilled
register, un compilo en mode optimisation brutale ne va pas arrondir les
valeurs de 80 bits a 64 bits tant qu'il reste dans les registres flottants.

Sauf erreur de ma part, ca n'a pas encore ete mentionne dans cette discussion.
Je sais que tu sais aussi bien que moi comment ca fonctionne, mais pour
certains des autres intervenants, je pense qu'il est utile de les detromper:
les archis a comportement IEEE754 totalement norme et utilise, c'est pas
si frequent que ca, et dans la plupart des cas, il vaut mieux eviter d'esperer
un comportement trop strict et conforme du compilateur et du processeur
(deja, les nombres denormalises, c'est rarement implemente completement
et correctement)
Avatar
Vincent Lefevre
Dans l'article <hpai6g$267v$,
Marc Espie écrit:

In article <20100330214159$,
Vincent Lefevre wrote:
>Dans l'article <4bafbb44$0$16460$,
> Samuel DEVULDER écrit:
>
>> Emmanuel a écrit :
>
>> > int main(void) {
>> > float x;
>> > x = 1.7;
>> > return ((x + 0.1) == 1.8) ? 1 : 0;
>> > }
>> >
>> > j'obtiens :
>> >
>> > $ gcc -Wall -o essai essai.c; ./essai; echo $?
>> > 0
>
>> Attention, le 1.7 de x est un float ici (23bits de précision), et le 0.1
>> (et le 1.8) de l'addition (ou de la comparaison) est un double (52bits
>> de précision).
>
>float = 24 bits de précision
>double = 53 bits de précision

Meme pas.



Attention, je parlais de la précision des types sur sa machine,
et non pas de la possible précision intermédiaire et des
éventuels bugs du compilateur (comme le fameux bug de GCC
sur les affectations et les casts).

>(à cause du bit implicite). Le 1.7 est un double, converti en float;
>c'est différent de 1.7f (ici, le résultat est le même, mais il peut
>y avoir des différences). Dans la pratique, cela peut être encore
>plus complexe.

Je pense qu'il convient ici de mentionner que ca peut etre encore plus
la merde, en particulier sur les architectures intel, ou la precision
des nombres stockes en memoire (double sur 8 octets) est differente de la
precisions des registres flottants (80 bits...). Du coup, la plupart des
compilo optimisants font des choses tres perturbantes. Rajouter un petit
bout de calcul quelque part *peut* influencer un calcul proche (hop, une
variable en trop, et hop, un spilled register, qui te fait passer une
valeur de 80 bits a 64 bits). Et bien evidemment, en l'absence de spilled
register, un compilo en mode optimisation brutale ne va pas arrondir les
valeurs de 80 bits a 64 bits tant qu'il reste dans les registres flottants.



C'est pour cela que j'ai dit: "Dans la pratique, cela peut être encore
plus complexe."

--
Vincent Lefèvre - Web: <http://www.vinc17.net/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.net/blog/>
Work: CR INRIA - computer arithmetic / Arénaire project (LIP, ENS-Lyon)