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
Antoine Leca
Samuel DEVULDER écrivit :
L'erreur vient peut être de celui qui attribue un context hyper
spécifique (C99, sizeof(long)==8) sur un truc où rien n'est précisé?



Euh... ton code (tel qu'il était) ne marchait pas non plus sur une
machine 16 bits [une de ces machines où printf("%x", un_long) n'a pas
l'effet escompté]. Je sais bien que c'était du code « jetable », et que
« le lecteur attentif rectifiera de lui-même », et je sais bien aussi
que la réponse de Vincent (%a) exige C99 (et je le déplore), mais elle a
au moins le mérite d'être sensiblement plus simple, et elle évite tous
les problèmes de numérotations des bits et des bytes qu'auraient une
version revue (avec unsigned char i[sizeof(double]) de l'union.


Antoine
Avatar
Vincent Belaïche
Salut,

J'ai trouvé un lien qui explique pourquoi MS reste sur une version plus
ancienne de la DLL

http://support.microsoft.com/kb/326922

ça posait des pb avec d'autres applications. Je vais essayer de mettre à
jour ma machine et faire l'essai.


Vincent.


Vincent Lefevre a écrit :
Dans l'article ,
Vincent Belaïche écrit:

En tout cas pour un truc (le printf("%a",...) ) spécifié depuis 10ans,
c'est surprenant que ce ne soit pas déjà de base dans MinGW (je crois
que j'ai installé sur cette machine MinGW depuis moins d'un an).



C'est de la faute de Microsoft:

http://oldwiki.mingw.org/index.php/C99
http://groupes.codes-sources.com/article-mingw-c99-fut-printf-amies-drosophiles-serrez-miches-2300.aspx

Avatar
Samuel DEVULDER
Antoine Leca a écrit :
Samuel DEVULDER écrivit :
L'erreur vient peut être de celui qui attribue un context hyper
spécifique (C99, sizeof(long)==8) sur un truc où rien n'est précisé?



Euh... ton code (tel qu'il était) ne marchait pas non plus sur une
machine 16 bits [une de ces machines où printf("%x", un_long) n'a pas
l'effet escompté].



J'ai une machine 16bits sous le coude et le printf %x marche plutôt bien
dessus. En l'occurrence le support des longs vient presque moins de la
machine que du compilo et des biliothèques de runtime (on peut
décomposer un long 32bits en 4 octets 8 bits. Ca change pas grand chose).

Le pb est de supposer des choses quand rien n'est spécifié. Certains
font l'hypothèse pessimiste et disent "ce code est faux", d'autres plus
pragmatiques disent "ce code marche" ou "c'est un exemple, on se fiche
des détails". Perso je n'aime pas les phrases sèches: "ce code est
faux". Il lui manque quelque chose indiquant sous quelle hypothèse c'est
faux. Un code peut être faux sous C99 et légitime sous C89. A l'inverse
il peut y avoir du code juste sous C99 qui ne le sera plus par la suite.

Je sais bien que c'était du code « jetable », et que
« le lecteur attentif rectifiera de lui-même », et je sais bien aussi
que la réponse de Vincent (%a) exige C99 (et je le déplore), mais elle a
au moins le mérite d'être sensiblement plus simple, et elle évite tous
les problèmes de numérotations des bits et des bytes qu'auraient une
version revue (avec unsigned char i[sizeof(double]) de l'union.



Exact, mais encore j'avais écrit à l'époque qu'on ne cherchais pas a
trouver l'exposant dans les bits affichés.

sam.
Avatar
Samuel DEVULDER
Antoine Leca a écrit :

Vincent est un universitaire; et qui plus est, un universitaire qui
travaille précisément sur ce sujet des nombres flottants, et qui publie.



Très bien. Il devrait donc être plus que parfait dans le domaine alors
et prodiguer des corrections précises et utiles à un code qu'il juge
incorrect. Et pas des "beurk", "c'est un bug", "ce code est faux", ou
"pseudo norme industrielles" sentencieux qu'on trouverai plus facilement
chez quelqu'un qui se la pète et veut à tout prix avoir le dernier mot.
J'avoue ne pas avoir apprécié son attitude d'alors.

Je ne t'apprend pas que dans le monde universitaire, « la littérature »
se réfère à l'ensemble des publications sur un sujet, et une partie
importante du travail de recherche est justement de trier dans « la
littérature » le bon grain de l'ivraie, pour ensuite intégrer dans ses
propres publications (par référence) la « bonne » littérature.

Je ne sais pas si dans ta pensée, le mot littérature ci-dessus avait ce
sens ;



Non je parlais du WEB. C'est clair depuis le début. Je me cite à nouveau:

Moi:
> Vincent:
> 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? 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).




mais je suis à peu près sûr que Vincent, lui, l'a lu dans ce
sens; et donc il t'a donné un conseil qui a d'autant plus de valeur



Oui peut être, mais en quoi le background change ce qu'on dit? Est-ce
qu'un type ultra compétant est plus utile qu'un crétin quand tous les
deux disent "Ce code est faux, cf C99"? Moi l'avis d'un type compétant
qui se contente d'une phrase aussi lapidaire ne vaut pas mieux que celle
de l'autre crétin.

qu'il a une longue expérience de cette fameuse littérature à laquelle tu
faisais référence.



Son conseil aurait été plus utile s'il avait filé l'url ou, mieux,
recopié le passage de la norme qui s'y réfère. Je l'ai fait, il dit que
ca parle de "binary exponent", je ne l'ai pas retrouvé. :-/

Certes. Mais comme il s'agit tout autant d'un exposant binaire
(puissance de deux qui modifie la mantisse), il y a ambiguïté.



Oui il peut y avoir, car la base peut être celle de numération ou de
l'exposant. Pas simple je suis d'accord. Mais j'ai précisé dans mon
message que le "p" du format %a signifiait "deux à la puissance".

Une autre ambiguïté est que le mot exposant peut signifier soit la
valeur entière, soit le résultat après exponentiation. Enfin, là c'est
ma lecture du sujet, peut-être que je me trompes ici.

Les documents auxquels tu fais référence insistent (et je le comprend
tout-à-fait, car ce n'est pas évident, surtout du fait du contexte) sur
la forme --en base 10, donc-- de la valeur de l'exposant ;



C'est ce qu'on veut savoir quand on fait un printf (le fait que ce soit
binaire en mémoire on s'en tamponne pour une sortie).

la position de Vincent, et je le comprend comme étant la vision des
spécialistes du domaine (dont je ne suis pas), est de mettre plus
d'emphase sur la différence avec le format %e (celui des calculatrices
scientifiques), en particulier le choix de la base.



Oui oui. Et d'ailleurs sa remarque sur le fait d'utiliser "%a" pour voir
la partie fractionnaire en hexa était bienvenue (même s'il n'a pas dénié
faire une phrase correcte pour expliquer cela se contentant d'un simple
"cf. C99").

sam.
Avatar
Samuel DEVULDER
Jean-Marc Bourguet a écrit :
Samuel DEVULDER writes:

L'erreur vient peut être de celui qui attribue un context hyper spécifique
(C99, sizeof(long)==8) sur un truc où rien n'est précisé?



Hyper-spécifique? Le type qui charge un Linux grand public pour voir pour
un processeur pas trop obsolète a une chance non négligable de se retrouver
dans cet environnement.



Bah oui c'est spécifique car ca ne rentre pas dans le contexte général
de la question. Je répète: si le code est faux, il faut:
1/ préciser dans quel contexte
2/ proposer une version qui marche.

D'ailleurs sur ce point je voulais te remercier pour le commentaire du
31/03:

Pour éviter ces problèmes
unsigned char* ptr = (char*)&d;
for (i = 0; i < sizeof(double); ++i)
printf("%02x", ptr[i]);
si on ne dispose pas de %a.

C'est déjà un pas dans la bonne direction, merci.

sam.
Avatar
Samuel DEVULDER
Vincent Lefevre a écrit :

C'est pour cela que j'avais indiqué %a.



Oui et tu as été remercié pour cela. Pour ma part une petite copie du
paragraphe correspondant de la spec, ou au moins son URL aurait été
aussi super sympathique pour les malheureux qui n'auraient pas la norme
C99 comme livre de chevet. (cf les questions de Vincent Belaïche par la
suite le 31/03).

Encore faut il que ce soit une vraie erreur. Un bout de code juste pour
C89 pourra être considéré comme une erreur en C99.



C'est très rare.



Peut-être, mais depuis être très rare quand devient une raison valable?
Les machines ayant un CHAR_BIT de 12 sont rares elles aussi, et pourtant
j'en connais qui grognent quand ils voient écrit:
char buf[sizeof(double)];
parce que ça introduit des pad-bits pas super clairement.

Voici quelques exemples (non exhaustifs) de ce qui passait sous C89 mais
plus sous C99:

http://usenix.org/publications/login/2002-10/pdfs/mccluskey.pdf

Autre truc. C99 est plus stricte que C89. Normalement C89 laisse passer
ce qui suit, mais pas C99:
char *s=...
unsigned char *t=s; /* erreur avec C99 */

Autre trucs venant directement de K&R et devenus invalide au fil du
temps (et encore, je crois qu'on en discute encore de la validité de
telle constructions dans les milieux zotorizécomondi):
http://www.c-faq.com/struct/structhack.html
(c'est un truc qu'on trouvais dans les libs amiga il y a longtemps btw).

Alors où se situe l'erreur? Dans le code ou dans le fait que le
contexte du code ne soit pas précisé?



Quand on ne précise pas le contexte, c'est celui de la norme C ISO.
La seule norme C est actuellement la version de 1999 (a.k.a. C99).
C89 n'est plus une norme, même si on peut en parler...



Pourquoi ? Pas de contexte veut dire pas de contexte. Pourquoi plus C99
que C90? Parce que c'est la dernière officielle? Oui, mais c'est
peut-être pas la plus répandue. On pourrait aussi bien justement dire
qu'en l'absence de contexte ce soit justement la norme la plus courante
qui fasse référence. Alors K&R, ANSI/C89, C90, C95, C99 laquelle
l'emporte? Non le plus simple et que sans contexte signifie rien de
spécial. Chacun choisi ce qui l'arrange. Pas besoin de faire tant de
remue-ménage.

L'erreur vient peut être de celui qui attribue un context hyper
spécifique (C99, sizeof(long)==8) sur un truc où rien n'est précisé?



Si on ne précise pas sizeof(long), il faut considérer toutes les
possibilités, surtout celles existantes en pratique (il faudrait
aussi considérer les bits de padding éventuels, le cas CHAR_BIT
différent de 8...).



Oui tu es un pessimiste né :-)

sam.
Avatar
Samuel DEVULDER
Antoine Leca a écrit :
Samuel DEVULDER écrivit :
Ok c'est plus clair à présent, sauf que je ne vois pas pourquoi on a
laissé un choix arbitraire pour ce 1er digit.



Comme tu le dis si bien, c'est arbitraire.

Maintenant, il existe un monde avant 1999, et en particulier dans ce
monde réel il y a des implémentations (hardware) où les flottants sont
représentés en base 10, et d'autres (en particulier chez IBM SJMSB) en
base 16! Et il aurait été stupide de la part du comité C de décider
arbitrairement pour une représentation qui aurait été incompatible avec
l'existant, ou qui aurait arbitrairement compliqué les implémentations
existantes : imagine le cas où ta machine cause la base 16 mais ce que
te montre %a est différent d'un vidage mémoire... à cause d'un décalage
de 1 ou 2 bits.



En effet. Mais ce que je ne comprends pas, c'est que les machines
d'avant 1999 ne connaissent même pas le format "%a" donc je vois pas
pourquoi on s'en est préoccupé. Dans le fond on ne peut pas parler de
backward compatibilité avec un truc qui n'existait pas avant.

Cette histoire de lattitude laissée au niveau du 1er chiffre me laisse
perplexe. Une explication comme (un peu plus débile qu')une autre est
qu'ils ont voulus que les codeurs de printf() puissent faire en sorte
que M_PI et M_E se différencient dès le 1er chiffre en représentation
'%a' (3 pour l'un et 2 pour l'autre). Qui sait? Ce sont peut être de
joyeux lurons très rigolos les gars du comité...



de toute façon la base de l'exponentielle est 2 (p=pow(2,.)),



Bin non justement, le fond du problème est bien là.



Sisi pour le %a c'est ca, le p représente *toujours* une puissance de deux:
The exponent always contains at least one digit, and only as
manymore digits as necessary to represent the decimal exponent
of 2.
Par contre on peut imposer la puissance d'être multiple de 16 si on veut
un découpage de 4 en 4.


sam.

PS: En tout cas ce neswgroup est devenu bien vivant ces dernier temps.
Je m'en réjouirais presque, car derrière les querelles se posent des
vrais questions techniques.



Antoine
Avatar
Vincent Lefevre
Dans l'article <4bbb4fc0$0$23437$,
Samuel DEVULDER écrit:

>> Encore faut il que ce soit une vraie erreur. Un bout de code
>> juste pour C89 pourra être considéré comme une erreur en C99.
>
> C'est très rare.

Peut-être, mais depuis être très rare quand devient une raison valable?



J'aurais pu ajouter que les codes qui ne compilent plus en C99
sont dans leur très grande majorité des codes sales (je ne connais
même pas de code "propre" qui provoque une erreur en C99).

Et puis avoir une erreur du compilo est moins problématique qu'un
comportement indéfini.

Les machines ayant un CHAR_BIT de 12 sont rares elles aussi, et pourtant
j'en connais qui grognent quand ils voient écrit:
char buf[sizeof(double)];
parce que ça introduit des pad-bits pas super clairement.



Cela n'est pas forcément un problème. Et si la suite du code est faux,
ce n'est pas un exemple à suivre.

Voici quelques exemples (non exhaustifs) de ce qui passait sous C89 mais
plus sous C99:

http://usenix.org/publications/login/2002-10/pdfs/mccluskey.pdf



Du code C89 sale... D'ailleurs, certains exemples sont mauvais même
en C99, e.g. oubli du "return 0;".

Si on râle à propos du code incorrect, et même sale, c'est pour
une bonne raison...

Autre truc. C99 est plus stricte que C89. Normalement C89 laisse passer
ce qui suit, mais pas C99:
char *s=...
unsigned char *t=s; /* erreur avec C99 */



Ça me semble valide en C99. Pourquoi ne le serait-ce pas?

> Quand on ne précise pas le contexte, c'est celui de la norme C ISO.
> La seule norme C est actuellement la version de 1999 (a.k.a. C99).
> C89 n'est plus une norme, même si on peut en parler...

Pourquoi ?



Parce que C89 n'est plus une norme.

--
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
Antoine Leca
Samuel DEVULDER écrivit :
Antoine Leca a écrit :
Samuel DEVULDER écrivit :
L'erreur vient peut être de celui qui attribue un context hyper
spécifique (C99, sizeof(long)==8) sur un truc où rien n'est précisé?



Euh... ton code (tel qu'il était) ne marchait pas non plus sur une
machine 16 bits [une de ces machines où printf("%x", un_long) n'a pas
l'effet escompté].



J'ai une machine 16bits sous le coude et le printf %x marche plutôt bien
dessus.



[ Je ne sais pas si c'est exprès, mais bon, au cas où : ]

printf("%x", un_long)

pose un problème en général, c'est que la bibliothèque attend un
unsigned, et qu'on lui passe un objet d'un autre type (long) ; puisque
printf() est une fonction variadique, les prototypes du C ANSI/ISO ne
détectent pas « automatiquement » l'erreur de type de la même façon
qu'ils le font en temps normal ; et là où le problème devient brûlant,
c'est que sur les plateformes les plus répandues à l'heure actuelle, on
ne se rend pas compte du problème, car il se trouve que unsigned et long
ont la même représentation mémoire.

Sur une machine 16 bits, printf("%x", un_long) marche effectivement
(sauf cas /très/ particuliers), mais n'affiche pas le même résultat que
sur une machine « habituelle », et ÀMHA le résultat « faux » est celui
de la machine 16 bits.


Perso je n'aime pas les phrases sèches: "ce code est faux".



Je crois qu'on avait compris. Cependant, il ne m'a pas semblé avoir
écrit cela, et sûrement pas dans le mien message auquel tu réponds.


Il lui manque quelque chose indiquant sous quelle hypothèse c'est
faux.



Mmm. D'abord, je pense faire en général un effort pour indiquer, au
moins sommairement, pourquoi je considère qu'un code ne va donner le
résultat escompté. Il est vrai que c'est parfois trop bref, cf. supra.

Ensuite, un des principes de la norme C, c'est de définir « bon », qui
s'y épelle « strictement conforme », et qui est en gros défini comme
« produit le même effet partout » ; après, on peut ergoter sur faux par
rapport à strictement conforme, toussa (surtout que pour compliquer, on
a la notion de « programme conforme » qui signifie « accepté par un
compilateur C conforme », ce qui inclut donc tous les programmes Fortran
et assembleur compilables par GCC...)


Un code peut être faux sous C99 et légitime sous C89.



C'est vrai mais c'est plutôt rare ; et c'est le plus souvent à cause de
l'utilisation de fonctionnalités comme le int implicite, qu'il vaut
mieux corriger le plus vite possible. Un autre problème peut venir des
noms de fonctions standards introduits par C99, et là aussi dans la
pratique, le programmeur va être obligé de suivre le train (c'est pareil
pour les mots clés de C++ ou les fonctions de Posix).

Après, je sais très bien qu'il y a des programmes écrits pour C89,
considérés comme bien écrits, qui posent problème lorsqu'on les passe
sur un compilateur C99 : le plus évident sont les (quelques) programmes
qui s'appuient sur la certitude que long est le plus grand type entier ;
cependant, je crois qu'ils servent beaucoup plus souvent d'alibi pour
cacher une multitude de programmes mal écrits, par exemple qui supposent
que long est un entier sur 32 bits.


Antoine
Avatar
Antoine Leca
Samuel DEVULDER écrivit :
Antoine Leca a écrit :
Je ne sais pas si dans ta pensée, le mot littérature ci-dessus avait ce
sens ;



Non je parlais du WEB. C'est clair depuis le début.



Je suis probablement un autre crétin, et certainement d'un autre siècle,
mais il ne m'est jamais venu à l'idée de lire « web » derrière
« littérature » ; même scientifique.

Je dis tout cela pour que comprennes un peu nos réactions, il n'y a pas
d'acrimonie, il y a juste parfois des différences d'interprétation.

Cela étant, la norme Posix que tu cites (site opengroup.org) est
effectivement un exemple de littérature au sens où je l'ai décris
(et je suppose que tu lus l'introduction de cette page, marqué CX).


Oui peut être, mais en quoi le background change ce qu'on dit?



? Si quelqu'un te dit « Viens ici ! », est-ce que ta manière de réagir
change selon que
- il s'agit de ta mère (et tu as 5 ans) ;
- il s'agit d'un policier en tenue ;
- il s'agit d'une femme également « en tenue », vers Pigalle ;
- il s'agit d'un homme visiblement éméché.

Je n'ai pas dit que Vincent rentre dans une quelconque de ces cases !


Est-ce qu'un type ultra compétant est plus utile qu'un crétin quand
tous les deux disent "Ce code est faux, cf C99"?



Je me referai au conseil à propos des ouvrages de référence sur les
flottants. Là tu as changé le contexte (et je suppose que tu voulais
écrire « compétent »).

Par ailleurs, il y a un « style Usenet » (que l'on rencontre aussi dans
les courriers électroniques), qui cultive un laconisme esthétique. Ce
style se perd ; les littéraires traditionalistes s'en réjouiront,
d'autres y voient la mort de Usenet ; mais il paraît difficile
aujourd'hui de le fustiger, surtout sur Usenet, il faut donc faire avec.


Son conseil aurait été plus utile s'il avait filé l'url ou, mieux,
recopié le passage de la norme qui s'y réfère. Je l'ai fait, il dit que
ca parle de "binary exponent", je ne l'ai pas retrouvé. :-/



6.4.4.2 (et par conséquent 7.20.1.3)

Le fait que l'on trouve "decimal exponent of 2" dans 7.19.6.1 (et 7.24)
ne me semble pas cohérent. Mais c'est un avis purement personnel.
De toute manière, toute formulation aura une part d'ambiguïté.


Les documents auxquels tu fais référence insistent (et je le comprend
tout-à-fait, car ce n'est pas évident, surtout du fait du contexte) sur
la forme --en base 10, donc-- de la valeur de l'exposant ;



C'est ce qu'on veut savoir quand on fait un printf (le fait que ce soit
binaire en mémoire on s'en tamponne pour une sortie).



Pas exactement. C'est en fait tellement gros que tu le considère comme
évident, mais tu as _aussi_ besoin de savoir quelle est la base de la
numération (B=2 pour %a) pour comprendre comment interpréter le nombre
m×B^e ; en effet 16 aurait aussi bien pu être utilisé, car après tout
les chiffres de la mantisse sont en base 16, eux.

Par contre, c'est vrai que le fait que ce soit stocké en mémoire en base
2, 16 ou 10 n'a aucune espèce d'importance.


Antoine