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

Erreurs d'arrondis avec des floats

9 réponses
Avatar
Daniel Déchelotte
Bonjour,

Mon code manipule des floats et contient quelques assertions du type

g_assert(cout1 > cout2);

A cause des inevitables erreurs d'arrondis, j'ecris en fait :

#define EPSILON_FLOAT 2e-6
g_assert(cout1 - cout2 > -EPSILON_FLOAT);

Vaut-il mieux tester le "signe" de la difference cout1 - cout2, ou bien
comparer cout1 + EPSILON_FLOAT et cout2 ? (si cela fait une quelconque
difference).

J'ai progressivement augmente EPSILON_FLOAT de facon a ce que mon programme
ne crashe plus (!). Sachant que mon programme fait de l'ordre de 5
additions de floats pour trouver ces couts, et que les grandeurs manipulees
sont de l'ordre de [0.1 -- 10.0], a partir de quelle valeur d'EPSILON_FLOAT
je dois considerer qu'en fait il s'agit d'un probleme algorithmique et pas
d'une erreur d'arrondi ?

--
Daniel Déchelotte
http://yo.dan.free.fr/

9 réponses

Avatar
Targeur fou
Daniel Déchelotte wrote:
Bonjour,


Bonjour,


Mon code manipule des floats et contient quelques assertions du type

g_assert(cout1 > cout2);

A cause des inevitables erreurs d'arrondis, j'ecris en fait :

#define EPSILON_FLOAT 2e-6
g_assert(cout1 - cout2 > -EPSILON_FLOAT);


Ca veut dire que cout2 est plus grand que cout1. Il faut prendre la
valeur absolue et une solution utilisant la fonction double
fabs(double); dans math.h serait plus appropriée.

Ce qui donnerait g_assert(fabs(cout1-cout2) > EPSILON_FLOAT); Je crois
que c'est dans la FAQ.

Vaut-il mieux tester le "signe" de la difference cout1 - cout2, ou
bien

comparer cout1 + EPSILON_FLOAT et cout2 ? (si cela fait une
quelconque

difference).


Valeur absolue.

J'ai progressivement augmente EPSILON_FLOAT de facon a ce que mon
programme

ne crashe plus (!). Sachant que mon programme fait de l'ordre de 5
additions de floats pour trouver ces couts, et que les grandeurs
manipulees

sont de l'ordre de [0.1 -- 10.0], a partir de quelle valeur
d'EPSILON_FLOAT

je dois considerer qu'en fait il s'agit d'un probleme algorithmique
et pas

d'une erreur d'arrondi ?


Tu as dans l'en-tête float.h une macro sympathique au doux nom de
FLT_EPSILON pour des float (en gros le quantum de précision), que
vaut-elle dans ton implémentation C par rapport à ton EPSILON_FLOAT ?
M'est d'avis que tu cherches trop de précision.
#include <math.h>
#include <float.h>
/*...*/
g_assert(fabs(cout1-cout2) > FLT_EPSILON); /* sera pas mal. */
/*...*/

Regis

Avatar
Targeur fou

#define EPSILON_FLOAT 2e-6
g_assert(cout1 - cout2 > -EPSILON_FLOAT);


Ca veut dire que cout2 est plus grand que cout1. Il faut prendre la
valeur absolue et une solution utilisant la fonction double
fabs(double); dans math.h serait plus appropriée.


J'ai été trop vite. Il se PEUT que cout2 soit plus grand que cout1
pour que l'assertion ci-dessus soit vraie. Mais ce n'est de toutes
façons pas la bonne approche avec des flottants. Il faut raisonner en
valeurs absolues.

M'est d'avis que tu cherches trop de précision.


Là j'ai dit une grosse connerie par contre. Mais ca ne change pas le
reste de mon précédent propos, à savoir utiliser FLT_EPSILON.

Regis


Avatar
Daniel Déchelotte
Salut, et merci de ta/tes reponses. Je limite mes lignes a 70
caracteres parce que ton logiciel m'a salement detruit les miennes :o)


| > > #define EPSILON_FLOAT 2e-6
| > > g_assert(cout1 - cout2 > -EPSILON_FLOAT);
| [...]
|
| J'ai été trop vite. Il se PEUT que cout2 soit plus grand que cout1
| pour que l'assertion ci-dessus soit vraie. Mais ce n'est de toutes
| façons pas la bonne approche avec des flottants. Il faut raisonner
| en valeurs absolues.

Je vais avoir besoin de plus d'explications parce que je ne vois pas
comment. Mon raisonnement a ete :

1_ Je veux tester cout1 > cout2
2_ C.-a-d. cout1 - cout2 > 0
3_ On rend le test plus permissif : cout1 - cout2 > -0.0xxx1

Avec des valeurs aboslues, je ne vois pas.

| > M'est d'avis que tu cherches trop de précision.

Bien possible. J'ai pu trouver :

#define FLT_EPSILON 1.19209290e-07F

donc j'imagine qu'apres quelques operations, qui de temps en temps
ajoutent leurs erreurs au lieu de les compenser, on peut arriver a une
erreur de 2e-6 sans que ce ne soit dramatique.

--
Daniel Déchelotte
http://yo.dan.free.fr/
Avatar
Horst Kraemer
Daniel Déchelotte wrote:

Bonjour,

Mon code manipule des floats et contient quelques assertions du type

g_assert(cout1 > cout2);

A cause des inevitables erreurs d'arrondis, j'ecris en fait :

#define EPSILON_FLOAT 2e-6
g_assert(cout1 - cout2 > -EPSILON_FLOAT);


Alors ton g_assert serait "vrai" si par exemple

cout1=0.999999 et cout2=1.0

et tu interprètes ce cas comme "cout1 est plus grand que cout2".

On ne peut pas conseiller une bonne route sans connaitre l'intérêt
mathématique du g_assert(cout1 > cout2) original qui fait planter ton
programme.

--
Horst

Avatar
Targeur fou
Salut, et merci de ta/tes reponses. Je limite mes lignes a 70
caracteres parce que ton logiciel m'a salement detruit les miennes
:o)



| > > #define EPSILON_FLOAT 2e-6
| > > g_assert(cout1 - cout2 > -EPSILON_FLOAT);
| [...]
|
| J'ai été trop vite. Il se PEUT que cout2 soit plus grand que
cout1

| pour que l'assertion ci-dessus soit vraie. Mais ce n'est de toutes
| façons pas la bonne approche avec des flottants. Il faut raisonner
| en valeurs absolues.

Je vais avoir besoin de plus d'explications parce que je ne vois pas
comment. Mon raisonnement a ete :

1_ Je veux tester cout1 > cout2


OK

2_ C.-a-d. cout1 - cout2 > 0


OK (0.0, ce sont des flottants)


3_ On rend le test plus permissif : cout1 - cout2 > -0.0xxx1


Peut être trop permissif.

cout1 = 1e-7
cout2 = 2e-7

cout1-cout2 = -1e-7, ce qui est inférieur en valeur absolue au quantum
FLT_EPSILON (cf plus bas, il vaut environ 1.2e-7). Le compilo va
considérer que ce sont les "mêmes" (la différence sera 0.0) et
l'assertion cout1-cout2>"valeur négative" sera vraie même si c'est
faux. C'est pour ça que ça ne va pas.

Avec des valeurs aboslues, je ne vois pas.


C'est plus simple et tu es (a peu près) sûr de ne pas te tromper. Tu
sais que tu pourras considérer 2 flottants égaux à partir du moment
où la différence entre les deux n'excèdera pas une résolution
maximale (ou mini, ca dépend comment on le perçoit) fournie par
l'implémentation.

| > M'est d'avis que tu cherches trop de précision.


Il est en fait plus probable que ce fut le contraire ;-), exemple en
appui.

Bien possible. J'ai pu trouver :

#define FLT_EPSILON 1.19209290e-07F

donc j'imagine qu'apres quelques operations, qui de temps en temps
ajoutent leurs erreurs au lieu de les compenser, on peut arriver a
une

erreur de 2e-6 sans que ce ne soit dramatique.


C'est plus complexe que cela, ca dépend aussi de macros (voir
FLT_ROUNDS notamment tjs dans float.h). Arriver à une erreur de 2e-6
sur une variable par rapport à sa valeur initiale n'est en effet pas
bien difficile, mais savoir qu'une différence entre deux 2 variables
(en val. absolue) sera facilement supérieure à 2e-6 après moults
calculs l'est beaucoup moins.

Regis

Avatar
Targeur fou
Salut, et merci de ta/tes reponses. Je limite mes lignes a 70
caracteres parce que ton logiciel m'a salement detruit les miennes
:o)



| > > #define EPSILON_FLOAT 2e-6
| > > g_assert(cout1 - cout2 > -EPSILON_FLOAT);
| [...]
|
| J'ai été trop vite. Il se PEUT que cout2 soit plus grand que
cout1

| pour que l'assertion ci-dessus soit vraie. Mais ce n'est de
toutes


| façons pas la bonne approche avec des flottants. Il faut
raisonner


| en valeurs absolues.

Je vais avoir besoin de plus d'explications parce que je ne vois
pas


comment. Mon raisonnement a ete :

1_ Je veux tester cout1 > cout2


OK

2_ C.-a-d. cout1 - cout2 > 0


OK (0.0, ce sont des flottants)


arghh !! 0.0F bien sûr, (0.0 c'est un double). Décidément.



3_ On rend le test plus permissif : cout1 - cout2 > -0.0xxx1


Peut être trop permissif.

cout1 = 1e-7
cout2 = 2e-7

cout1-cout2 = -1e-7, ce qui est inférieur en valeur absolue au
quantum

FLT_EPSILON (cf plus bas, il vaut environ 1.2e-7). Le compilo va
considérer que ce sont les "mêmes" (la différence sera 0.0) et
l'assertion cout1-cout2>"valeur négative" sera vraie même si c'est
faux. C'est pour ça que ça ne va pas.

Avec des valeurs aboslues, je ne vois pas.


C'est plus simple et tu es (a peu près) sûr de ne pas te tromper.
Tu

sais que tu pourras considérer 2 flottants égaux à partir du
moment

où la différence entre les deux n'excèdera pas une résolution
maximale (ou mini, ca dépend comment on le perçoit) fournie par
l'implémentation.

| > M'est d'avis que tu cherches trop de précision.


Il est en fait plus probable que ce fut le contraire ;-), exemple en
appui.

Bien possible. J'ai pu trouver :

#define FLT_EPSILON 1.19209290e-07F

donc j'imagine qu'apres quelques operations, qui de temps en temps
ajoutent leurs erreurs au lieu de les compenser, on peut arriver a
une

erreur de 2e-6 sans que ce ne soit dramatique.


C'est plus complexe que cela, ca dépend aussi de macros (voir
FLT_ROUNDS notamment tjs dans float.h). Arriver à une erreur de 2e-6
sur une variable par rapport à sa valeur initiale n'est en effet pas
bien difficile, mais savoir qu'une différence entre deux 2 variables
(en val. absolue) sera facilement supérieure à 2e-6 après moults
calculs l'est beaucoup moins.

Regis



Avatar
Jean-Marc Bourguet
Daniel Déchelotte writes:

Mon code manipule des floats


Ça commence mal, généralement on fait les calculs en double
et les float ne servent que pour le stockage.

et contient quelques assertions du type

g_assert(cout1 > cout2);

A cause des inevitables erreurs d'arrondis, j'ecris en fait :

#define EPSILON_FLOAT 2e-6
g_assert(cout1 - cout2 > -EPSILON_FLOAT);


Autoriser l'égalité est vraissemblablement inévitable, mais
je vérifierais que changer l'ordre l'est bien.

J'ai aussi un doute en voyant EPSILON_FLOAT utilisé comme
une erreur absolue maximale plutôt qu'une erreur relative
maximale.
cout1 - cout2 > -EPSILON_FLOAT*max(cout1, cout2)

Vaut-il mieux tester le "signe" de la difference cout1 -
cout2, ou bien comparer cout1 + EPSILON_FLOAT et cout2 ?
(si cela fait une quelconque difference).


Dans l'hypothèse où EPSILON_FLOAT est petit par rapport à
cout1 et cout2, comparer cout1-cout2 avec EPSILON_FLOAT est
plus précis que comparer cout1 + EPSILON_FLOAT à cout2.

J'ai progressivement augmente EPSILON_FLOAT de facon a ce
que mon programme ne crashe plus (!). Sachant que mon
programme fait de l'ordre de 5 additions de floats pour
trouver ces couts, et que les grandeurs manipulees sont de
l'ordre de [0.1 -- 10.0], a partir de quelle valeur
d'EPSILON_FLOAT je dois considerer qu'en fait il s'agit
d'un probleme algorithmique et pas d'une erreur d'arrondi?


Si j'ai bonne mémoire, il y a moyen de faire des additions
de sorte que le résultat optenu soit le résultat exact (donc
en supposant que les termes sont exacts) arrondi à la
précision des flottants utilisés (ou alors l'erreur est au
plus un ULP).

En considérant les données comme exactes, je réflichirais en
détail si j'avais une erreur supérieure à 2 ou 3 ULP, donc 3
(mon chiffre) * 10.0 (ton maxima) * FLT_EPSILON (dans
float.h) soit environ 3E-6 avec les floats courants. Mais
comme je l'ai déjà écrit, j'aurais tendance à utiliser une
borne relative.

A+

--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Antoine Leca
<news:,
Daniel Déchelotte va escriure:

Mon code manipule des floats et contient quelques assertions du type
<...>

#define EPSILON_FLOAT 2e-6


N'oublie pas que EPSILON_FLOAT est un double, pas un float.


Antoine

Avatar
Targeur fou
Bonjour,


Bonjour

[coupé]


Pour revenir sur le principe de faire des calculs sur des doubles,
j'ai

essaye de passer les types de "delta" et "tmp" de float a double,
sans

aucune amelioration de la precision. Je ne comprends pas pourquoi. En
revanche, utiliser des doubles pour les totaux finaux font gagner en
precision un facteur 100. Hmm...


J'ai regardé ton fichier joint. Il y a deux lignes ou tu perds
(potentiellement) de la précision quand tu sommes un float et un
double et que tu mets le résultat dans un float.

a_total = a_cost + delta; et d_total = d_cost + delta;

Ce sont tous des float sauf delta (et tmp). Tu réalises à chaque fois
3 affectations d'opérations d'addition/soustaction de float dans la
varaible delta. Tu as gagné en précision dès la première
affectation (pour chaque calcul de delta) mais il est possible que tu
la perdes avec ces deux lignes (i.e si en terme de plage et de
précision, la valeur d'un double peut être mise dans un float, le
résulat dans le float sera inchangé (pas de perte), mais encore faut
t'il que ce soit le cas, pas bien dur pour la valeur mais pour la
précision ce n'est pas sûr du tout).

PJ: rounding.c
Compilation: gcc -W -Wall -o rounding rounding.c
Execution:
3.219389e+01 != 3.219388e+01 (diff = 3.814697e-06 = 32.00 *
FLT_EPSILON)


Une telle différence est sans doute liée à la perte de précision
(float to double puis résultat mis en float).

1.018313e+00 == 1.018313e+00 (diff = 1.986821e-08 < FLT_EPSILON =
1.192093e-07)


Ici il n'y a pas beaucoup de différence, tu n'as travaillé qu'avec
des float et est donc resté dans la même précision.

Regis