Précision des calculs
Accueil

du site

Au menu :

Quelle est la précision des calculs réalisés par un ordinateur ?

Un calcul effectué par un ordinateur donne-t-il toujours exactement le même résultat
que celui que laissent prévoir les mathématiques ?

Pourquoi certaines boucles basées sur l'atteinte d'une limite par une variable
"tournent' - elles indéfiniment ?

Une erreur incompréhensible !


Programme de démonstration :

#include <stdio.h>

int main()
{
;;double d = 0;

;;while(d != 1)
;;{
;;;;printf("%f\n",d);
;;;;d = d + 0.1; // d += 0.1 serait mieux !
;;}
;;return 0;
}

Affichage

0.000000
0.100000
0.200000
0.300000
........
0.900000
1.000000
1.100000
1.200000
........
231.800000
231.900000
232.000000
232.100000
..........
Tourne en boucle indéfiniment

C'est paradoxal, la valeur d = 1.0000 a bel et bien été obtenue et la boucle ne s'est pas arrêtée !

Il faut se souvenir que le processeur qui effectue les calculs en format flottant
ne peut donner que des résultats approximatifs.

L'ordinateur mémorise les nombres réels (au sens des mathématiques)
dans des registres de plusieurs octets (4, 8, 10 octets suivant la précision).
Au sens informatique, ce sont des flottants (type float) ou des flottant double précision (type double).
Voir représentation des nombres réels en informatique au lien :

Or, certains nombres réels ont une représentation numérique infinie.
Exemples :
1/3 =0.33333333333333333...etc
pi= 3,1415926535897932384626433832795... etc
etc.

Dès lors,
il devient impossible de représenter exactement de tels nombres
par un nombre fini de bits.
Le compilateur est donc obligé à des approximations.

Dans une fonction complexe comportant de multiples opérations et variables
les approximations cumulent.

Le principe de la détermination de la précision sur le résultat final
peut se déterminer grâce à la relation entre différentielle d'une fonction multivariables
et dérivées partielles de la fonction par rapport à chacune des variables
et les différentielles des variables.
.



Au-delà, voir un cours de mathématiques ...


Sur le plan pratique : pour arrêter la boucle précédente.


Il suffit de remplacer (d != 1) par (d < 1)

#include <stdio.h>

int main()
{
;;double d = 0;

;;while(d < 1)
;;{
;;;;printf("%f\n",d);
;;;;d = d + 0.1; // d += 0.1 serait mieux !
;;}
;;return 0;
}

Affichage :

0.000000
0.100000
0.200000
0.300000
0.400000
0.500000
0.600000
0.700000
0.800000

0.900000
1.000000
-

Boucle arrêtée pour la valeur affichée "1.000000":

Ce phénomène n'affecte pas les entiers


Si nous évaluons la condition de sortie de boucle par rapport à un entier égal au flottant,
la boucle s'arrête.

#include <stdio.h>

int main()
{
;;double d = 0;
;;int i = 0;

;;while(i < 1)
;;{
;;;;printf("%f %i\n",d,i);
;;;;d = d + 0.1; // d += 0.1 serait mieux !
;;;;i = d;

;;}
;;return 0;
}

Affichage :

0.000000 0
0.100000 0
0.200000 0
0.300000 0
0.400000 0
0.500000 0
0.600000 0
0.700000 0
0.800000
0
0.900000 0
1.000000 0
-

Boucle arrêtée pour la valeur affichée "d= 1.000000":
Remarquez que l'opération : i = d
tronque la valeur de "d" ; c.a.d. la débarasse de sa partie décimale.-
i == 0 tant que d < 1
Mais la dernière ligne affiche d == 1.000000 avec i == 0.
Que comprendre ?
En fait, "d" n'est pas exactement égal à 1.000000, mais la fonction "printf"
affiche une approximation par valeur supérieure.

Ce que montre bien notre premier exemple dont la boucle ne s'arrêtait jamais
pour "d == 1.000000" pour la bonne raison que "d" ne prend jamais cette valeur exactement
vu l'imprécision des calculs par le proceseur numérique..

Les calculs effectués sur des entiers sont d'une exactitude totale.

Sur les flottants ( des types "float" ou "double" ) ils sont imprécis, ce qui peut avoir
des conséquences fâcheuses :

  • sur la précision des résultats
  • sur les opérations de comparaison (boucles)
  • sur l'affichage par des fonction de type "printf" ou autres.

Précision dans le domaine des flottants


Tout compilateur doit indiquer dans sa fiche technique avec quelle présision il effectue les calculs.

Par exemple, Micrsoft™ Visual C++ V 6 possède deux fichiers d'entête :
" float.h " et " limits.h "

dans lesquels sont définies des constantes (plutôt des macros)
utilisables dans les programmes et concernant les valeurs numériques extrêmes
exploitables par le compilateur..

Elles sont très nombreuses, nous n'en extrayons ici que quelques-unes,
celles en relation avec la question que nous étudions actuellement.

Constante
Valeur mathématique
Type concerné
FLT_EPSILON
1.192092896e-07F
float
DBL_EPSILON
2.2204460492503131e-016
double
LDBL_EPSILON
2.2204460492503131e-016
long double
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
FLT_MIN
1.175494351e-38F
float
DBL_MIN
2.2250738585072014e-308
double
LDBL_MIN
2.2250738585072014e-308
long double
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .

EPSILON est le nom généralement attribué à une constante liée à chaque système
de calcul numérique digital et qui est sa "résolution".

Par définition, EPSILON est la plus petite valeur x pour laquelle : x+1.0 est différent de 1.0.

MIN est le plus petit nombre positif que l'on puisse prendre en compte numériquement.
Ce qui signifie que si vous écrivez :
float n = 1.175e-38F
le résultat ne sera pas différent de n = 0.0;

 

Remarques

Remarquez l'écriture 1.0 à la place de 1.
Lorsque vous écrivez "1" le compilateur ne sait pas s'il faut lui attribuer le type integer ou float.
Deux configurations de mémorisation très dfférentes - voir flottants
En écrivant 1.0 le doute est levé : c'est un flottant.

Le F de 1.175494351e-38F peut vous étonner.
En fait c'est la même démarche que ci-dessus.
Cette écriture force le compilateur à prendre la constante suivie de F comme de type "float"
Sinon elle serait prise comme de type "double".

Ce sont des subtilités du compilateur utilisé.

Attention ! ces valeurs et conventions varient d'un compilateur à l'autre
et même d'une version à l'autre du même compilateur,
alors ... à vos notices !


Sommaire Langage C C++
Accueil

du site