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

1 2 3 4 5
Avatar
Vincent Lefevre
Dans l'article <4bb391db$0$10479$,
Samuel DEVULDER écrit:

Vincent Lefevre a écrit :

>> Sans doute. Mais si tu as des exemples concrets ca m'interresse
>
> Le cas classique:
>
> if (x != y)
> a = b / (x - y);
> else
> ...

Perso je fais pas confiance au x!=y pour la division, car si ca diffère
d'un pouieme sensiblement égal à la précision, le résultat de la
division contient une erreur énorme sur la valeur de "a" (plusieurs
ordres de grandeurs au dessus).



*peut* contenir. Il y a des cas où on sait par exemple que x et y
contiennent des valeurs exactes, auquel cas l'erreur sur x - y
est nulle (lemme de Sterbenz). C'est loin d'être le cas le plus
courant, mais ça peut arriver. Il y a des cas où l'erreur peut être
acceptable (e.g. cas d'un terme correctif), car elle disparaîtra
plus ou moins dans la suite des calculs, ou parce que c'est un cas
rare et l'application peut alors accepter certaines imprécisions,
etc. Bref, le but essentiel était d'éviter une division par 0 (qui
produit une exception -- au sens de la norme IEEE 754), qui pourrait
entraîner un comportement non contrôlable dans la suite des calculs
(plantage, etc.).

Et n'oublions pas qu'une des raisons pour lesquelles les dénormalisés
ont été requis par la norme IEEE 754 est d'avoir la propriété
ci-dessus.

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

>> car en fait je me dit que si on veut une équalité stricte, alors il
>> est plus simple de passer par un union et comparer (bit à bits) les
>> entiers correspondants.
>
> Non! D'une part c'est incorrect, car les représentations ne sont pas
> forcément identiques

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

Et si tu veux faire le même genre de chose en décimal (IEEE 7554-2008
et WG14/N1312), ça ne marchera plus, car les décimaux ne sont pas
forcément normalisés (cf cohortes).

> (encore plus s'il y a des bytes de padding, comme avec un long
> double en 80 bits dans une union). D'autre part, ce serait plus
> lent.

Ca n'est pas le propos. Le propos était de voir si les deux "double"s
sont identiques (sous entendu en mémoire).



On n'utilise pas toujours que des double. Conseiller un code qui
fonctionne pour des double mais pas pour d'autres types n'est
généralement pas une bonne chose.

Et puis il y a de toute façon le problème de performance: un test
d'égalité n'a pas besoin d'avoir à faire passer des registres
flottants dans des registres entiers via la mémoire.

--
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
Jean-Marc Bourguet
Vincent Belaïche writes:

Bonjour,

Si j'ai bien compris %a est le spécificateur format de printf pour afficher
un float en base 16.

Y a-t-il un site où on peut trouver les réponses aux questions suivantes:

Question 1 : pour les doubles c'est %la ?



C'est %a -- mais %la est aussi possible. Pour les long double, c'est %La
(exactement comme pour les autres formats flottants).

Question 2 : quelle est la lettre pour l'exposant en hexa
(e comme en décimal?)



Rien de prevu.

Question 3 : y a-t-il un équivalent des notations fixe, scientifique, et
ingénérie (%f, %e et %g)



Non.

Question 4 : Comment écrit-on une constante immédiates float en hexa
Est-ce que 0x0.1f est équivalent à 0.0625f, 0x0.1 à
0.0625?



Gagne.

Question 5 : En C++ (pas en C) un ostream de gcc formate -0.0
(0.0 avec un bit de signe = 1) en `-0.0', alors que
VisualC++ le formate en `0.0'. Je ne sais pas ce que fait
printf, mais comment ça devrait être ? Et en général,
est-ce qu'un 0.0 avec un bit de signe à 1 est une valeur
valide ?



C'est valide; je ne suis pas sur que l'affiche soit specifie (et j'ai pas
le temps de regarder).
--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Vincent Lefevre
Dans l'article <4bb3886a$0$20650$,
Samuel DEVULDER écrit:

Peut-etre.. qu'est il préconisé à la place? Parce que les codes pour
parser la partie fractionnaire où l'on trouve:
double b = 0.1;
while(..) {
x += ((*s++) - '0') * b;
b /= 10;
}
sont légions et contiennent tous le même bug.



Ce code est mauvais. Pour bien faire, de la multiprécision est
nécessaire (e.g. en entier ou avec rationnels exacts).

Dans notre "Handbook of Floating-Point Arithmetic", on donne justement
un tel algorithme de conversion, en fait base 2 - base 10 (algo 2.2
page 45).

> Le code est tout de même faux sur la plupart des machines actuelles
> (avec des long sur 64 bits)!

Hum? Je ne vois pas. D'un coté tu as 2 long int à 32 bits chacun, et de
l'autre 64bits. Ca doit rentrer.



Relis ce que je dis: je parle de long sur 64 bits (cas le plus courant
sur machines 64 bits). Sur un telle machine, un double a la même
taille qu'un seul long et non pas 2. Si tu stocke un double et que
tu lis 2 long, tu te retrouves avec un comportement indéfini.

--
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 <4bb39751$0$30019$,
Samuel DEVULDER écrit:

Samuel DEVULDER a écrit :

> long *ptr = &d;

Pour les amateurs de trétacapillotomie[*], on peut écrire:

long *ptr = (void*)&d;



Ça ne change rien à l'affaire: si tu stockes un double et que tu
le relis en tant qu'entier via un long *, ce n'est pas conforme
(problème d'aliasing). Et ce n'est pas que théorique. Un certain
nombre de codes ne fonctionnent plus correctement lorsqu'ils sont
compilés avec le snapshot de GCC. La solution est de passer par
une union (ou un tableau d'unsigned char).

--
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
Jean-Marc Bourguet
Samuel DEVULDER writes:

Vincent Belaïche a écrit :
> Bonjour,
> Si j'ai bien compris %a est le spécificateur format de printf pour
> afficher un float en base 16.

Je peux pas te répondre sur tout. Le fait est que le format %a n'est pas
référencé partout. Trop récent peut être.



La norme date de 1999.

> Y a-t-il un site où on peut trouver les réponses aux questions suivantes:
> Question 1 : pour les doubles c'est %la ?

Je sais pas. J'arrive pas à trouver de truc clair la dessus. Un approche
empirique a l'air de dire que gcc accepte double et float sans soucis. Le
code asm montre cependant que le float est converti en double avant d'être
envoyé sur la pile pour le printf. Ca relativise l'intérêt de "%a" pour
regarder ce qu'on a *exactement* en mémoire car une conversion a lieu.



printf et consorts sont des fonctions variadiques, donc les arguments
additionnels sont promus, entre autre float vers double.

Apparemment il faut toujours le 'p'.



Exact -- (j'ai commis l'erreur de me fier a ma memoire tout a l'heure).

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
Vincent Lefevre
Dans l'article ,
Jean-Marc Bourguet écrit:

- on ne lit pas le dernier membre écrit dans l'union, à nouveau non
conforme.



C'est autorisé par la norme C (ce qui ne l'est pas, c'est de faire
le même genre de chose via des pointeurs), mais partiellement du
domaine de l'implementation-defined.

--
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 <4bb3c03b$0$27951$,
Samuel DEVULDER écrit:

Je sais pas. J'arrive pas à trouver de truc clair la dessus. Un approche
empirique a l'air de dire que gcc accepte double et float sans soucis.
Le code asm montre cependant que le float est converti en double avant
d'être envoyé sur la pile pour le printf. Ca relativise l'intérêt de
"%a" pour regarder ce qu'on a *exactement* en mémoire car une conversion
a lieu.



Il y a conversion uniquement pour float -> double, mais cette
conversion ne doit pas changer la valeur de la variable, donc on
sait exactement ce qu'on a (sauf qu'on ne peut pas différencier
les différents NaN éventuels, mais c'est tout).

On dirait que "%a" affiche tous les quartets (tranche de 4bits) du
double sauf ceux de poids faibles ne contenant que du zero. C'est
l'équivalent d'une représentation décimale où l'on affiche pas les zero
terminaux.



Pas exactement, au sens où le premier quartet peut avoir 4 valeurs
possibles, suivant que l'on considère qu'il y a 0, 1, 2 ou 3 zéros
en tête (les implémentations diffèrent). En effet, l'exposant est
binaire, i.e. on a:

signe * significande_en_hexa * 2^exposant

> Question 3 : y a-t-il un équivalent des notations fixe, scientifique, et
> ingénérie (%f, %e et %g)

Je sais pas.



Le format de %a est assez spécifique, plus proche de %e que des
autres, je dirais.

> Question 4 : Comment écrit-on une constante immédiates float en hexa
> Est-ce que 0x0.1f est équivalent à 0.0625f, 0x0.1 à
> 0.0625?

Apparemment il faut toujours le 'p'. Le format ne semble pas super
libre. La partie entière est 0 (pour 0.0 exclusivement) ou 1 (pour les
autres nombres), suivi de la partie fractionnaire précédée par un '.'
(si non nulle).



À l'affichage, la partie "entière" peut aller de 0 à 15 (0 à F en
hexa). 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.

Il faut croire que VC++ estime que -0.0 est 0.0 c'est pareil (ce qui
n'est pas faux) et l'affiche de façon normalisée.



Ce n'est pas pareil (si la norme IEEE 754 est suivie), mais les
valeurs sont égales. Je crois qu'il est connu que VC++ ne suit
pas la norme IEEE 754 sur ce point (on a eu des problèmes avec
ce compilo pour MPFR).

--
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
Vincent Belaïche écrivit :
Si j'ai bien compris %a est le spécificateur format de printf pour
afficher un float en base 16.



Oui

Y a-t-il un site où on peut trouver les réponses aux questions suivantes:



Ici me paraît la bonne option ! (enfin, sauf la dernière)

Question 1 : pour les doubles c'est %la ?



Normalement c'est %a. %la marche aussi (comme pour tous les formats
flottants), parce qu'à l'origine "long float" était un synonyme de
"double", il s'agit donc d'un vestige que tu peux utilement oublier.
De toute manière, printf ne manipule que des double (les float sont promus).

Question 2 : quelle est la lettre pour l'exposant en hexa
(e comme en décimal?)



p (et P avec %A)

Question 3 : y a-t-il un équivalent des notations fixe, scientifique, et
ingénérie (%f, %e et %g)



%a est l'équivalent de %e (mais la précision par défaut n'est pas figée
à 6, elle est plutôt « la bonne valeur » (pour avoir une représentation
avec toute l'expressivité possible); il n'y a pas d'équivalent de %f ou
%g (dont je ne vois pas le rapport avec l'ingénierie, c'est juste un
format plus élaboré qui combine %e et %f).


Question 4 : Comment écrit-on une constante immédiates float en hexa



Surprise ! de la même façon que printf écrit avec %a. Étonnant, non ?
Bon, il y a pas mal de raccourcis possibles, comme par exemple la
possibilité d'omettre le signe d'un exposant positif.

Est-ce que 0x0.1f est équivalent à 0.0625f, 0x0.1 à
0.0625?



0x.1p0f est équivalent à 0x1p-4f et 0x100p-12f et effectivement 0.0625f
Le p n'est pas facultatif (le suffixe f n'est pas suffisant pour
transformer en nombre flottant en C, c'est vrai pour les décimaux aussi)


Question 5 : En C++ (pas en C)



... cela ressemble à un éventuel problème de conformité entre deux
implémentations différentes de la bibliothèque standard C++. Désolé.


Antoine
Avatar
Samuel DEVULDER
Vincent Lefevre a écrit :

On dirait que "%a" affiche tous les quartets (tranche de 4bits) du
double sauf ceux de poids faibles ne contenant que du zero. C'est
l'équivalent d'une représentation décimale où l'on affiche pas les zero
terminaux.



Pas exactement, au sens où le premier quartet peut avoir 4 valeurs
possibles, suivant que l'on considère qu'il y a 0, 1, 2 ou 3 zéros
en tête (les implémentations diffèrent). En effet, l'exposant est



heu? je ne pige pas. Tu veux dire qu'on peut avoir 0x100p0 pour
représenter 256.0 ? C'est quoi ces zéros de têtes.. je parlais des zéros
de queue (bref comme en décimal: on écrit 0.1 et pas 0.10000000).

binaire, i.e. on a:

signe * significande_en_hexa * 2^exposant




../..


À l'affichage, la partie "entière" peut aller de 0 à 15 (0 à F en
hexa).



Ah oui ok, donc pas forcément 1.quelquechose.. suivant les runtimes.

C'est bizarre d'avoir autre chose que "1." au début. On peut donc avoir
0x8p-3 ou 0x1p0 pour représenter 1.0 au runtime. C'est pas génial une
représentation qui admet deux affichages pour le même nombre entre deux
runtimes différent. :-/ Normalement on s'attendrait que "%a" affiche
toujours (à peu près) la même chose pour un même nombre entre 2 runtimes
différents. C'est une curiosité ce truc.

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

a, A
A double argument representing a floating-point number shall be
converted in the style "[-]0xh.hhhhp±d", where there is one hexadecimal
digit (which shall be non-zero if the argument is a normalized
floating-point number and is otherwise unspecified) before the
decimal-point character and the number of hexadecimal digits after it is
equal to the precision; if the precision is missing and FLT_RADIX is a
power of 2, then the precision shall be sufficient for an exact
representation of the value; if the precision is missing and FLT_RADIX
is not a power of 2, then the precision shall be sufficient to
distinguish values of type double, except that trailing zeros may be
omitted; if the precision is zero and the '#' flag is not specified, no
decimal-point character shall appear. The letters "abcdef" shall be used
for a conversion and the letters "ABCDEF" for A conversion. The A
conversion specifier produces a number with 'X' and 'P' instead of 'x'
and 'p'. The exponent shall always contain at least one digit, and only
as many more digits as necessary to represent the **decimal exponent of
2**. If the value is zero, the exponent shall be zero.


Il faut croire que VC++ estime que -0.0 est 0.0 c'est pareil (ce qui
n'est pas faux) et l'affiche de façon normalisée.



Ce n'est pas pareil (si la norme IEEE 754 est suivie), mais les



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

valeurs sont égales.



Encore heureux :)

sam.
Avatar
Antoine Leca
Samuel DEVULDER a écrit :
Vincent Lefevre a écrit :

On dirait que "%a" affiche tous les quartets (tranche de 4bits) du
double sauf ceux de poids faibles ne contenant que du zero. C'est
l'équivalent d'une représentation décimale où l'on affiche pas les
zero terminaux.



Pas exactement, au sens où le premier quartet peut avoir 4 valeurs
possibles, suivant que l'on considère qu'il y a 0, 1, 2 ou 3 zéros
en tête (les implémentations diffèrent). En effet, l'exposant est



heu? je ne pige pas. Tu veux dire qu'on peut avoir 0x100p0 pour
représenter 256.0 ?



Non. Ce que veut dire Vincent, c'est que pour écrire 256.0, tu peut (au
choix de l'implémentation) récupérer n'importe lequel de
0x1p+8
0x2p+7
0x4p+6
0x8p+5
(fait de tête, sans machine)

C'est quoi ces zéros de têtes..



Une conséquence du fait que 16 est une puissance quatrième d'un nombre
premier (ce qui n'est pas le cas de 10 ou de sa décomposition, ni non
plus de 2, évidemment).
Donc le « chiffre avant la virgule » n'est pas unique, il y a quatre
possibilité (la puissance ci-dessus)


Si tu écris en base 360 (2³×3²×5), tu aurais 3 (la puissance de 2) fois
2 (celle de 3) égalent 6 possibilités pour le premier chiffre (avec la
même règle que celle de la norme C99) !


Antoine
1 2 3 4 5