OVH Cloud OVH Cloud

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



On le trouve, j'allais dire, quasi partout :-/ Une solution moins pire
serait de lire cela comme un entier et diviser par la puissance de 10
qui va bien au final. On remplace plein d'erreurs liées à la
multiplications par 0.1 par une seule au final. Ca donnerait un truc du
genre

char *s= "1245.1";
double res = 0, div = 1;
while(*s>='0' && *s<='9') res = res*10 + (*s++ - '0');
if(*s=='.') {
++s;
while(*s>='0' && *s<='9') {
res = res*10 + (*s++ - '0');
div *= 10;
}
res /= div;
}

(bon évidemment dans un vrai code, on stopperait quand l'addition du
digit ne change plus aucun bit au nombre. Cad quand on a atteint la
précision maxi).

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



Oui.. mais ca doit rammer un peu. L'idéal est probablement la
représentation des floats en hexa. Normalement si on imprime tous les
bits il ne peut y avoir de soucis. Il faut promouvoir le "0xh.hhhpdd"
dans les chaumières!

Mais ca ne facilitera pas la lecture.. quoique.. qui lit les floats?
Normalement on a un #define à la place et on lit M_PI et pas 3.14159...

Une autre option serait d'avoir une représentation flottante en base
dix. IEEE 754-2008 le supporte,

http://en.wikipedia.org/wiki/Decimal32_floating-point_format

mais c'est peut-etre trop récent. Avant cela il y avait IEEE 854-1987.
Mais c'est plus un truc de hardware que de C cette affaire. Le plus
proche qui ressemble à cela au niveau langage de programmation que je
connais sont les BigDecimals java.


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



Heu.. oui mais "long int" c'est 32bits chez moi. Pour du 64bit j'écris
"long long int". J'ai loupé un truc? Je n'ai jamais parlé de machine
64bits. Je vois pas d'où elle sort cette babasse là.

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.

sam.

PS: au fait ne pas confondre, Misra est aussi une miss-India.. et elle
ne doit pas spécialement s'intéresser à la programmation en C entre les
séances photos ;)
Avatar
Samuel DEVULDER
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 ?

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.

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.

../..


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.




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.

sam.
Avatar
Samuel DEVULDER
Antoine Leca a écrit :
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..





En fait les zeros de têtes sont les *bits* à zero qu'on décide de placer
dans la partie avant le point.

Punaise. Zero et bit à zero c'est pas pareil! Sans déconner ca aide pas
à comprendre.. moi je parlais des zéros hexa en fin de nombre et d'un
autre coté on parle de bit à zero au début, tout cela en parlant de zéro
comme si il n'y avait qu'un seul zéro. Pas facile!

Ok c'est plus clair à présent, sauf que je ne vois pas pourquoi on a
laissé un choix arbitraire pour ce 1er digit. On aurait pris le 1
universel (c'est le même dans toutes les bases), et toujours là dans le
cadre flottant IEEE (sauf pour le vrai zero), on aurait eu une seule
façon de représenter un nombre et je pense que ca n'aurait pas changé
grand chose aux routines de printf/scanf (voir ca les aurait simplifiées).

Il n'y a pas de raison de grouper le 1er digit en 4 ou 1 bit.. de toute
façon la base de l'exponentielle est 2 (p=pow(2,.)), donc ca signifie
qu'on travaille avec un alignement de 1bit fondamentalement. Je dis pas,
si on avait dit que p signifiait 16, alors là oui ca aurait eu du sens
de dire que le 1er digit est dans 0..9a..f.. mais avec un alignement au
bit près, on aurait dit que c'était toujours le digit de poids fort,
j'aurais trouvé ca super logique. Mais bon on a peut être voulu faire
plaisir à ceux qui voulaient que M_PI commence par un 3 aussi quand il
est représenté en flottant base 16.

sam (bits à zéro, tête à toto!)
Avatar
Samuel DEVULDER
Vincent Lefevre a écrit :

compilés avec le snapshot de GCC. La solution est de passer par



Heureusement qu'il n'y a pas que GCC dans le marché (même pas fichu de
faire du code VLE sur MPC55xx, ou de compiler sans manquer de registres
sur la gamme issue des 68xx).

une union (ou un tableau d'unsigned char).



Il faudrait savoir.. Union ou pas union? D'un coté j'écris un union et
ca grogne chez les curés^h^hspécialistes, de l'autre on passe par un
pointeur et ca grogne autant.

Vincent, Jean-Marc: accordez vos violons.

sam (de toute façon on s'en fiche, c'est un exemple pour jouer)
Avatar
Jean-Marc Bourguet
Vincent Lefevre writes:

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.



J'allais dire que ce n'etait pas clair du tout que ce soit le cas, mais le
DR 283 clarifie en effet la chose.

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

Vincent Lefevre a écrit :

> compilés avec le snapshot de GCC. La solution est de passer par

Heureusement qu'il n'y a pas que GCC dans le marché (même pas fichu de
faire du code VLE sur MPC55xx, ou de compiler sans manquer de registres sur
la gamme issue des 68xx).

> une union (ou un tableau d'unsigned char).



Union est valable -- plus precision il y a des cas defini par
l'implementation et des cas indefinis (au moins en C, il faudra que je
regarde en C++ pour voir s'il y a une difference sur ce point entre les
langages)

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 Belaïche
Dans la documentation de emacs calc les notations resemblant à à %f, %e,
et %g sont repsectivement appelé fix, scientific, et engineering.

Voir le noeud d'info de Calc "(Calc) Float Formats"

Ceci dit, rien à voir avec IEEE754 : les flottant de Calc sont en base
10 et en précision arbitraire.


Antoine Leca a écrit :
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).





(Le sujet m'interesse car je viens de coder des fonctions de conversion
entre les flottant d'emacs Calc et les flottant IEEE754 natif en emacs
lisp, ceux correspondent au type C double --- ça devrait un jour ou
l'autre rentrer dans emacs)


Ci-après un copier-collé d'un extrait du noeud de Calc ci-dessus référencé:

The `d f' (`calc-fix-notation') command selects fixed-point
notation. The numeric argument is the number of digits after the
decimal point, zero or more. This format will relax into scientific
notation if a nonzero number would otherwise have been rounded all the
way to zero. Specifying a negative number of digits is the same as for
a positive number, except that small nonzero numbers will be rounded to
zero rather than switching to scientific notation.

The `d s' (`calc-sci-notation') command selects scientific notation.
A positive argument sets the number of significant figures displayed,
of which one will be before and the rest after the decimal point. A
negative argument works the same as for `d n' format. The default is
to display all significant digits.

The `d e' (`calc-eng-notation') command selects engineering
notation. This is similar to scientific notation except that the
exponent is rounded down to a multiple of three, with from one to three
digits before the decimal point. An optional numeric prefix sets the
number of significant digits to display, as for `d s'.
Avatar
Vincent Belaïche
Salut,

Je suis moi même fort étonné que la convention soit que la mantisse de
tout nombre non nul est comprise dans [1 16[.

La fonction frexp renvoie toujours une mantisse dans [0.5 1[ (pour tout
nombre non nul, non NaN, non infini). C'est surprenant que la même
convention n'a pas été prise, c'est à dire que que 0x.1p0 ne soit pas la
façon standard de représenter 0,0625.

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.

Vincent.

Samuel DEVULDER a écrit :
Antoine Leca a écrit :
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..





En fait les zeros de têtes sont les *bits* à zero qu'on décide de placer
dans la partie avant le point.

Punaise. Zero et bit à zero c'est pas pareil! Sans déconner ca aide pas
à comprendre.. moi je parlais des zéros hexa en fin de nombre et d'un
autre coté on parle de bit à zero au début, tout cela en parlant de zéro
comme si il n'y avait qu'un seul zéro. Pas facile!

Ok c'est plus clair à présent, sauf que je ne vois pas pourquoi on a
laissé un choix arbitraire pour ce 1er digit. On aurait pris le 1
universel (c'est le même dans toutes les bases), et toujours là dans le
cadre flottant IEEE (sauf pour le vrai zero), on aurait eu une seule
façon de représenter un nombre et je pense que ca n'aurait pas changé
grand chose aux routines de printf/scanf (voir ca les aurait simplifiées).

Il n'y a pas de raison de grouper le 1er digit en 4 ou 1 bit.. de toute
façon la base de l'exponentielle est 2 (p=pow(2,.)), donc ca signifie
qu'on travaille avec un alignement de 1bit fondamentalement. Je dis pas,
si on avait dit que p signifiait 16, alors là oui ca aurait eu du sens
de dire que le 1er digit est dans 0..9a..f.. mais avec un alignement au
bit près, on aurait dit que c'était toujours le digit de poids fort,
j'aurais trouvé ca super logique. Mais bon on a peut être voulu faire
plaisir à ceux qui voulaient que M_PI commence par un 3 aussi quand il
est représenté en flottant base 16.

sam (bits à zéro, tête à toto!)
Avatar
Vincent Belaïche
Salut,

Je suis moi même fort étonné que la convention soit que la mantisse de
tout nombre non nul est comprise dans [1 16[.

La fonction frexp renvoie toujours une mantisse dans [0.5 1[ (pour tout
nombre non nul, non NaN, non infini). C'est surprenant que la même
convention n'a pas été prise, c'est à dire que que 0x.1p0 ne soit pas la
façon standard de représenter 0,0625.

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.

Vincent.

Samuel DEVULDER a écrit :
Antoine Leca a écrit :
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..





En fait les zeros de têtes sont les *bits* à zero qu'on décide de placer
dans la partie avant le point.

Punaise. Zero et bit à zero c'est pas pareil! Sans déconner ca aide pas
à comprendre.. moi je parlais des zéros hexa en fin de nombre et d'un
autre coté on parle de bit à zero au début, tout cela en parlant de zéro
comme si il n'y avait qu'un seul zéro. Pas facile!

Ok c'est plus clair à présent, sauf que je ne vois pas pourquoi on a
laissé un choix arbitraire pour ce 1er digit. On aurait pris le 1
universel (c'est le même dans toutes les bases), et toujours là dans le
cadre flottant IEEE (sauf pour le vrai zero), on aurait eu une seule
façon de représenter un nombre et je pense que ca n'aurait pas changé
grand chose aux routines de printf/scanf (voir ca les aurait simplifiées).

Il n'y a pas de raison de grouper le 1er digit en 4 ou 1 bit.. de toute
façon la base de l'exponentielle est 2 (p=pow(2,.)), donc ca signifie
qu'on travaille avec un alignement de 1bit fondamentalement. Je dis pas,
si on avait dit que p signifiait 16, alors là oui ca aurait eu du sens
de dire que le 1er digit est dans 0..9a..f.. mais avec un alignement au
bit près, on aurait dit que c'était toujours le digit de poids fort,
j'aurais trouvé ca super logique. Mais bon on a peut être voulu faire
plaisir à ceux qui voulaient que M_PI commence par un 3 aussi quand il
est représenté en flottant base 16.

sam (bits à zéro, tête à toto!)
Avatar
Vincent Lefevre
Dans l'article <4bb4e703$0$20724$,
Samuel DEVULDER écrit:

> Relis ce que je dis: je parle de long sur 64 bits (cas le plus courant
> sur machines 64 bits).

Heu.. oui mais "long int" c'est 32bits chez moi. Pour du 64bit j'écris
"long long int". J'ai loupé un truc? Je n'ai jamais parlé de machine
64bits. Je vois pas d'où elle sort cette babasse là.



Moi j'en parle: ton code avec long int n'est pas portable, et
utiliser long long ne l'est pas non plus (meme s'il l'est dans
la pratique avec les machines actuelles). Il faudrait utiliser
des types de <stdint.h>.

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.

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