Routines de comparaison des nombres à virgule flottante

De RAD Studio
Aller à : navigation, rechercher

Remonter à Utilisation des routines des nombres à virgule flottante


Lors de la comparaison de nombres à virgule flottante, plusieurs problèmes non évidents se posent.

Problèmes liés aux comparaisons de nombres à virgule flottante

L'un des problèmes de la comparaison des nombres à virgule flottante est que ce que vous voyez ne correspond pas forcément à la façon dont les nombres sont représentés en mémoire. Les nombres X := 99.9999964 et Y "= 100.000003 peuvent tous deux être affichés sous la forme 100 alors qu'ils ne sont pas égaux :

 uses
   System.SysUtils;
 var X, Y: Single;
 begin
   X := 99.9999964;
   Y := 100.000003;
   Writeln('X =', FloatToStr(X));
   Writeln('Y =', FloatToStr(Y));   ReadLn;
 end.

Sortie du programme :

X = 100
Y = 100

Toutefois, si vous essayez de comparer X et Y, ils ne sont pas égaux. Voici un exemple inverse :

 var X:Single;
 begin
   X:=0.1;
   Writeln('X =', FloatToStr(X));
 end.

Sortie du programme :

X = 0.100000001490116

Un autre problème naît de la représentation limitée des nombres à virgule flottante. Comparons un nombre à virgule flottante avec 'lui-même' :

 var X:Single;
 begin
  X:=0.1;
  if X=0.1 then
   Writeln('Equal')
  else
   Writeln('Not equal');
  ReadLn;
 end.

Sortie du programme :

Not equal

Pourquoi ? Pour comprendre cela, rappelez-vous que le nombre décimal exact 0.1 a une représentation binaire équivalente à la forme d'une fraction récurrente infinie 0.0(0011). La valeur X en simple précision ne stocke que 23 bits pour la mantisse. Delphi exécute toutes les opérations à virgule flottante en utilisant une précision étendue. Lorsque Delphi convertit X en simple précision vers la représentation étendue ayant une mantisse de 63 bits, le programme initialise simplement tous les bits supplémentaires avec des zéros (pas avec les bits réels de 0.0(0011)). Pour plus d'informations, lire Implications de la précision limitée.

A présent, comparons 0.1 en simple précision et en double précision :

 var
    X:Single;
    Y:Double;
 begin
  X:=0.1;
  Y:=0.1;
  if X=Y then
   Writeln('Equal')
  else
   Writeln('Not equal');
  ReadLn;
 end.

Sortie du programme :

Not equal

Là encore, les représentations en simple précision et en double précision de 0.1 stockent un nombre différent de bits de mantisse.

Routines de comparaison

Pour comparer correctement des nombres à virgule flottante, la solution consiste à utiliser une petite marge Epsilon. Si l'écart entre les deux nombres à virgule flottante se situe dans la marge Epsilon, alors ils sont considérés comme égaux.

L'unité System.Math fournit les fonctions CompareValue, SameValue et IsZero qui gèrent les comparaisons des nombres à virgule flottante. Les déclarations de ces fonctions s'apparentent aux suivantes :

 function CompareValue(const A, B: Extended; Epsilon: Extended): TValueRelationship;
 function SameValue(const A, B: Extended; Epsilon: Extended): Boolean;
 function IsZero(const A: Extended; Epsilon: Extended): Boolean;

Ici, A et B sont les nombres à virgule flottante à comparer et Epsilon est la petite marge d'écart pouvant exister entre A et B pour qu'ils soient considérés comme ayant la même valeur.

Si vous spécifiez Epsilon = 0, une valeur par défaut raisonnable est implicitement utilisée. Par exemple, la version Extended (étendue) de SameValue utilise la valeur par défaut :

Epsilon = Max(Min(Abs(A), Abs(B)) * 1E-16, 1E-16)

Voir aussi