Salut à tous,
J'apporte ma petite contribution (en Free Pascal
). Je vais parler d'une erreur commune souvent liée à une mauvaise appréciation des nombres flottants, le problème n'est pas lié à un langage en particulier.
Dans les calculs itératifs sur les réels, on est souvent amené à comparer 2 réels pour en vérifier la terminaison, l'erreur réside dans leur comparaison directe, en négligeant l'imprécision intrinsèque des flottants, on peut ainsi aboutir à des boucles infinies.
Pour illustrer ceci, je propose un calcul itératif simple pour laquelle la convergence est connue, il s'agit de la somme d'une suite géométrique de raison q, 0<q<1 (je vous renvoie à vos livres de maths préférés
). La limite est facile à calculer, elle vaut q/(q-1). Le calcul itératif ne sert pas à grand chose puisqu'on connaît le résultat général mais il permet de mettre en évidence le problème.
Le code est le suivant (programme complet):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
|
#!/usr/bin/instantfpc
// supprimer la 1ère ligne pour Delphi
program Flottants;
{$mode objfpc}{$H+} // Supprimer cette ligne pour Delphi
uses
Classes, SysUtils;
const
Values : array[1..17] of Extended = ( 2, 3, 5, 7, 11, 13, 17, 19,
23, 29, 31, 37, 41, 43, 47, 53,
101);
MaxIter = 100000;
LimiteAtteinte : array[boolean] of String = ('Non', 'Oui');
function ANePasFaire(const N: Extended): Integer;
var
P, // le terme de la suite
S, // somme des termes
L: Extended; // Valeur de la limite
begin
if N = 0 then raise EDivByZero.Create('Erreur: Argument nul.'); // pas le choix, ici
S := 1;
P := 1;
L := N / (N - 1);
Result := 1; // Compteur d'itération
// Pb sur (S<>L)!!!, heureusement, les itérations sont contrôlées
while (S <> L) and (Result < MaxIter) do
begin
P := P / N;
S := S + P;
Inc(Result);
end;
End;
// On ne compare pas entre eux des nombres flottants
function PlusCorrect(const N: Extended): Integer;
const
Precision : Extended = 1E-9;
var
P, // le terme de la suite
S, // somme des termes
L: Extended; // Valeur de la limite
begin
if N = 0 then raise EDivByZero.Create('Erreur: Argument nul.'); // pas le choix, ici
S := 1;
P := 1;
L := N / (N - 1);
Result := 1; // Compteur d'itération
// On compare la différence à une précision donnée
while (Abs(S - L) >= Precision) and (Result < MaxIter) do
begin
P := P / N;
S := S + P;
Inc(Result);
end;
End;
var
v: Extended;
nIter: Integer;
begin
WriteLn('** Début de test *********');
WriteLn(' ** Code fautif *********');
for v in Values do // instruction non vérifiée dans Delphi
begin
nIter := ANePasFaire(v);
WriteLn(Format(' %6d itérations pour %3.0f, limite atteinte: %s', [ nIter, v, LimiteAtteinte[nIter<>MaxIter]]));
end;
WriteLn(' ** Code raisonnable ****');
for v in Values do // instruction non vérifiee dans Delphi
begin
nIter := PlusCorrect(v);
WriteLn(Format(' %6d itérations pour %3.0f, limite atteinte: %s (avec la précision voulue)', [ nIter, v, LimiteAtteinte[nIter<>MaxIter]]));
end;
WriteLn('** Fin de test ***********');
ReadLn;
end. |
Les résultats obtenus sont les suivants:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
** Début de test *********
** Code fautif *********
65 itérations pour 2, limite atteinte: Oui
100000 itérations pour 3, limite atteinte: Non
27 itérations pour 5, limite atteinte: Oui
100000 itérations pour 7, limite atteinte: Non
19 itérations pour 11, limite atteinte: Oui
100000 itérations pour 13, limite atteinte: Non
100000 itérations pour 17, limite atteinte: Non
100000 itérations pour 19, limite atteinte: Non
15 itérations pour 23, limite atteinte: Oui
14 itérations pour 29, limite atteinte: Oui
13 itérations pour 31, limite atteinte: Oui
13 itérations pour 37, limite atteinte: Oui
12 itérations pour 41, limite atteinte: Oui
100000 itérations pour 43, limite atteinte: Non
100000 itérations pour 47, limite atteinte: Non
100000 itérations pour 53, limite atteinte: Non
100000 itérations pour 101, limite atteinte: Non
** Code raisonnable ****
31 itérations pour 2, limite atteinte: Oui (avec la précision voulue)
20 itérations pour 3, limite atteinte: Oui (avec la précision voulue)
14 itérations pour 5, limite atteinte: Oui (avec la précision voulue)
11 itérations pour 7, limite atteinte: Oui (avec la précision voulue)
9 itérations pour 11, limite atteinte: Oui (avec la précision voulue)
9 itérations pour 13, limite atteinte: Oui (avec la précision voulue)
8 itérations pour 17, limite atteinte: Oui (avec la précision voulue)
8 itérations pour 19, limite atteinte: Oui (avec la précision voulue)
7 itérations pour 23, limite atteinte: Oui (avec la précision voulue)
7 itérations pour 29, limite atteinte: Oui (avec la précision voulue)
7 itérations pour 31, limite atteinte: Oui (avec la précision voulue)
6 itérations pour 37, limite atteinte: Oui (avec la précision voulue)
6 itérations pour 41, limite atteinte: Oui (avec la précision voulue)
6 itérations pour 43, limite atteinte: Oui (avec la précision voulue)
6 itérations pour 47, limite atteinte: Oui (avec la précision voulue)
6 itérations pour 53, limite atteinte: Oui (avec la précision voulue)
5 itérations pour 101, limite atteinte: Oui (avec la précision voulue)
** Fin de test *********** |
On constate que :
- pour le cas CANPF (fonction ANePasFaire) la limite n'est pas toujours atteinte avec un risque de boucle infinie.
- Cette limite peut être atteinte avec une précision raisonnable et un nombre d'itérations moindre en codant de manière adaptée (fonction PlusCorrect)
- La différence de code est minime, la mauvaise implémentation est donc difficilement justifiable.
Moralité : Eviter les comparaisons entre des nombres flottants issus de calculs (je ne parle pas des constantes).
4 |
0 |