Soucis avec les comparaisons de nombres réels

John Dogget Messages postés 384 Date d'inscription vendredi 18 juin 2004 Statut Membre Dernière intervention 7 mai 2009 - 7 déc. 2008 à 17:31
John Dogget Messages postés 384 Date d'inscription vendredi 18 juin 2004 Statut Membre Dernière intervention 7 mai 2009 - 15 déc. 2008 à 23:43
Salut à tous.

J'ecris une fonction qui transforme un nombre decimal en fraction non réduite (pour l'instant).
la technique est très simple, je multplie le nombre par 10,100,1000,x jusqu'à ce que la valeur après la virgule du produit soit nul.

Voici donc mon code :

function DecimalToFraction(NombreDecimal:extended):TFraction;
var
  Fraction:TFraction;
  Multiplicateur:int64;
  DecimalTemporaire:extended;
begin
  Multiplicateur:= 1;
  DecimalTemporaire:=NombreDecimal;
  while Frac(DecimalTemporaire)<>0 do
  begin
    Multiplicateur:=Multiplicateur*10;
    DecimalTemporaire:=NombreDecimal*Multiplicateur;
  end;
  Fraction.Numerateur:=Trunc(DecimalTemporaire);
  Fraction.Denominateur:=Multiplicateur;
  Result:=Fraction;
end;

Ca marche très bien pour certaines valeurs, pour d'autres delphi me fait n'importe quoi

Exemple avec le nombre decimal 1.4615 .

Le calcul se passe bien jusqu'au 4° passage dans la boucle, au dela Frac(DecimalTemporaire) me sors des valeurs bizarres.
A ce stade  on a normalement 1.4615*10000 =14615, et donc Frac(14615) devrait être nul en toute logique, c'est pas le cas apparement puisque delphi me dit que Frac(14615) est egal à ... 8.881[...]e-16

J'ai cru comprendre que c'etait une chose normale, que ça venait de la représentation des nombres réels ...
Sauf que dans mon cas, c'est très genant, en plus d'être archi faux mathematiquement.

15 réponses

Utilisateur anonyme
7 déc. 2008 à 18:08
Salut,

Ce code fonctionne nickel chez moi :

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TFraction=Record
    Numerateur:Extended;
    Denominateur:Extended;
  End;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Déclarations privées }
  public
    { Déclarations publiques }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

Function Retourne_Fraction(NombreDecimal:extended):TFraction;
Begin
  Result.Denominateur:=1;
  Result.Numerateur:=NombreDecimal;
  While Frac(Result.Numerateur)<>0 Do
   Begin
     Result.Denominateur:=Result.Denominateur*10;
     Result.Numerateur:=Result.Numerateur*10;
   End;
End;

procedure TForm1.Button1Click(Sender: TObject);
Var
  Fraction:TFraction;
begin
  Fraction:=Retourne_Fraction(1.4615);
  Showmessage(FloatToStr(Fraction.Numerateur));
  Showmessage(FloatToStr(Fraction.Denominateur));
end;
0
John Dogget Messages postés 384 Date d'inscription vendredi 18 juin 2004 Statut Membre Dernière intervention 7 mai 2009
7 déc. 2008 à 18:46
Hmm, tu as essayé avec d'autres valeurs qu'avec 1.4615 ?

Perso, je l'ai trouvé en utilisant une fonction test pour voir si le bug etait reproductible, une simple generation de nombre réels aleatoires avec une boucle sur la fonction qui calcul la fraction.

Dans mon cas ça aboutissait vite fait à un plantage de la fonction

Et aussi, le type TFraction que j'utilise est avec des int64, pas des extended comme tu le fait, tu penses que le problème vient de là ?

Tiens pis j'ai une autre question
Dans ta fonction tu travail directement avec le résultat de celle-ci, alors que moi je crée une variable de travail pour ça.
C'est une habitude ou bien ta soution est preferable à la mienne (rapidité, efficacité toussa)

Merci de ta réponse
0
Utilisateur anonyme
7 déc. 2008 à 19:06
J'ai essayé avec d'autres nombres et pas de plantages en vue

Pour le type utilisé j'ai pris un extended car c'est le type utilisé pour NombreDecimal. Il est pas impossible que ton probleme vient de cela.

Pour le Result que j'utilise : Ca évite d'utiliser une autre variable donc c'est un gain de performance. Cette technique est à bannir dans certains le cas de traitements lourds, ce qui n'est pas le cas ici .

Bon coding John
0
John Dogget Messages postés 384 Date d'inscription vendredi 18 juin 2004 Statut Membre Dernière intervention 7 mai 2009
7 déc. 2008 à 20:56
Haha !!

J'ai reproduit le bug avec ton code Francky
Avec la valeur que j'avais donné avant, mais aussi avec pleins d'autres : 4.2295, 3.8265 etc etc

Pour moi, il y a bel et bien un soucis avec la fonction Frac.
Par curiosité, tu utilises quelle version de delphi ?
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Utilisateur anonyme
7 déc. 2008 à 21:31
Je viens de tester tes 3 valeurs : je n'ai aucune erreur .
J'ai Delphi6 personnel : je pense pas que cela soit un beug de la fonction Frac.

Peux tu faire un copier coller exact de tout ton code stp que je teste avec ma version : merci .
0
John Dogget Messages postés 384 Date d'inscription vendredi 18 juin 2004 Statut Membre Dernière intervention 7 mai 2009
7 déc. 2008 à 21:45
Oki.

Bon alors une form avec un memo et un bouton, qui va tester la fonction de calcul des fractions.

Son code :

procedure TForm1.Button1Click(Sender: TObject);
var
  Compteur:byte;
  ReelAleatoire:extended;
  Fraction:TFraction;
begin
  Memo1.Clear;
  ReelAleatoire:=1.225;
  for Compteur:=0 to 20 do
  begin
    Memo1.Lines.Add('Calcul de la fraction correspondant à '+FloatToStr(ReelAleatoire));
    Fraction:=DecimalToFraction(ReelAleatoire);
    Memo1.Lines.Add('   '+FloatToStr(Fraction.Numerateur)+'/'+FloatToStr(Fraction.Denominateur));
    ReelAleatoire:=ReelAleatoire+0.2365;
  end;
end;

Ensuite une unité à part pour toutes les fonctions de calcul

unit Fractions;

interface

uses Dialogs, Math, SysUtils;

type TFraction=record
  Numerateur:extended;
  Denominateur:extended;
end;

type TNombreDecompose=array of int64;

function DecimalToFraction(NombreDecimal:extended):TFraction;

implementation

Function DecimalToFraction(NombreDecimal:extended):TFraction;
begin
  Result.Numerateur:=NombreDecimal;
  Result.Denominateur:=1;
  While Frac(Result.Numerateur)<>0 Do
   Begin
     Result.Denominateur:=Result.Denominateur*10;
     Result.Numerateur:=Result.Numerateur*10;
     ShowMessage(FloatToStr(Result.Numerateur)+#13+
                 FloatToStr(Result.Denominateur)+#13+
                 FloatToStr(Frac(Result.Numerateur)));
   End;
end;

end.
0
Utilisateur anonyme
7 déc. 2008 à 23:20
Aucun problème pour ma part sauf au niveau du formatage du résultat qui laisse à désirer à partir d'une certaine valeur (Mais ce n'est pas pour l'instant le probleme).

T'es sur que tu as pas autre chose dans ton code qui serait à l'origine du problème ? Ca me surprend que Borland est changé la fonction Frac. Au pire tu as qu'a la recoder toi meme : c'est pas bien dur  à faire et comme ca tu es sur de plus te prendre la tete si tu viens un jour à changer de version de Delphi.

Allez t'énerve pas : tout probleme est formateur
0
John Dogget Messages postés 384 Date d'inscription vendredi 18 juin 2004 Statut Membre Dernière intervention 7 mai 2009
7 déc. 2008 à 23:56
Ben c'est quand même bizarre qu'avec le même code, on n'arrive pas à la même chose.
Chez moi on voit clairement que la multiplication par dix se poursuit plus que necéssaire, et ce à cause de la fonction Frac puisque c'est elle qui contrôle "la manoeuvre".
Pour une raison que j'ignore Frac(un nombre au hasard)=0.00000000000000000000000000000000XYZ dans certains cas, et donc il ne donne pas 0 comme il devrait le faire

Par ailleurs, j'ai trouvé ceci : http://delphi.developpez.com/faq/?page=typenombre#comprarerreels
En gros ca explique que les comparaisons entre réels peuvent parfois mal tourner.

Je pense que je vais laisser tomber le côté mathematique de la chose, pour chercher une solution avec les chaines de caracteres
- on cherche le point de séparation decimal/entier
- on cherche la longueur de la partie arrière de la chaine (après la virgule donc)
- on calcul le denominateur avec (10 puissance longueur de la chaine)
- on efface le point de séparation dans la chaine de depart et on a le numerateur

C'est plus compliqué que la méthode mathematique, mais au moins ça marche ...
Tout ça pour mettre à jour une source que j'avais deposé ici il y aquelques temps, et la débarrasser de ses bugs
0
cs_Jean_Jean Messages postés 615 Date d'inscription dimanche 13 août 2006 Statut Membre Dernière intervention 13 décembre 2018 3
8 déc. 2008 à 06:06
Ta fonction John fonctionne chez moi aussi!
Je n'ai jamais eu de probème avec frac à la condition de bien savoir ce que l'on prend de l'extended qui pose toujours des problèmes de limite. Mais dans ton cas tu ne retraites pas le calcul à partir de la partie fractionnaire résultante précédente, donc il ne devrait pas y avoir d'erreur. Peut-être est-ce un bug local ou ton processeur!?
Pour la valeur 1.00000000000000000001 qui comprend 21 chiffres significatifs, le résultat est 0 car le 1 est dans lanature à la 21ème place.
Tiens nous au courant!
Cordialement
Jean_Jean
0
cs_Jean_Jean Messages postés 615 Date d'inscription dimanche 13 août 2006 Statut Membre Dernière intervention 13 décembre 2018 3
8 déc. 2008 à 06:45
@ John
Je n'ai pas vérifier comment était écrite la fonction Frac dans l'unité Math de delphi. 
J'utilisai perso la fonction suivante dans mon unité Maths :  
Function FRAC (X:Extended) : Extended;
Begin
  X:=X-Trunc(X);
  IF (X<0) THEN X:=X+1.0; 
  Result :=X
End;
Bien à toi!
Jean_Jean
0
John Dogget Messages postés 384 Date d'inscription vendredi 18 juin 2004 Statut Membre Dernière intervention 7 mai 2009
9 déc. 2008 à 22:09
Re-bonjour à tous

Hors donc, ma fonction ne fonctionnant bien que chez les autres (un comble), j'ai continué de chercher et j'ai fini par trouver une solution (tadaaaaa ).

Une petite lecture de ce site vous en dira plus : http://homepages.borland.com/efg2lab/Library/Delphi/MathInfo/index.html

Pour faire marcher mon code, j'ai dû utiliser la fonction IsZero de l'unité math.
Cette fonction renvoie true si un Extended passé en parametre se rapproche de zéro, le tout en fonction d'une tolérance Epsilon egalement passé en parametre.

Le hic c'est que je savais pas quelle valeur mettre pour epsilon, du coup j'y allais au pif et j'arrivais à rien.
Heureusement j'ai trouvé un code tout bête pour calculer cette tolérance en fonction de la machine, toujours sur le site donné plus haut.

Voila donc mon code final :

unit Fractions;

interface

uses Dialogs, SysUtils, Math;

type TFraction=record
  Numerateur:Int64;
  Denominateur:Int64;
end;

  procedure CalculerEpsilon;
  function DecimalToFraction(NombreDecimal:Real):TFraction;

var
  Epsilon:Extended;

implementation

procedure CalculerEpsilon;
var
  e:Extended;
  i:Integer;
begin
  Epsilon:=1;
  i:=0;
  repeat
    Epsilon:=Epsilon/2;
    e:=1+Epsilon;
    i:=i+1;
  until (e=1.0) or (i=50);
  Epsilon:=Epsilon*2;
end;

function DecimalToFraction(NombreDecimal:Real):TFraction;
var
  DecimalTemporaire:Real;
begin
  DecimalTemporaire:=NombreDecimal;
  Result.Denominateur:=1;
  while not IsZero(Frac(DecimalTemporaire),Epsilon) do
  begin
    Result.Denominateur:=Result.Denominateur*10;
    DecimalTemporaire:=10*DecimalTemporaire;
  end;
  Result.Numerateur:=Trunc(DecimalTemporaire);
end;

end.

Je me suis servi de la petite optimisation de varibales suggéré par francky.
Par contre j'ai gardé des types int64 pour les fractions, parce que ça représente mieux la réalité des fractions, et aussi parce que c'est plus précis que les extended

Maintenant, je vais m'attaquer à la reduction de la fraction, ça devrait être nettement moins dur
0
cs_Jean_Jean Messages postés 615 Date d'inscription dimanche 13 août 2006 Statut Membre Dernière intervention 13 décembre 2018 3
10 déc. 2008 à 00:57
Sans vérifier, j'aurai fait :

Function GetEpsilon : Extended;
var
  e:Extended;
  i:Integer;
begin
  result := 1;
  i:=0;
  repeat
    Result := Result /2;
    e:=1+Result;
    i:=i+1;
  until (e=1.0) or (i=50);
  Result := result *2;
end;
Je suppose que tu as vérifier le nombre de boucles nécessaire pour la précision!
Bien à toi!

Jean_Jean
0
John Dogget Messages postés 384 Date d'inscription vendredi 18 juin 2004 Statut Membre Dernière intervention 7 mai 2009
15 déc. 2008 à 20:36
Bon ça marche toujours pas
Jean-jean, je m'en vais essayer ta fonction !
0
cs_Jean_Jean Messages postés 615 Date d'inscription dimanche 13 août 2006 Statut Membre Dernière intervention 13 décembre 2018 3
15 déc. 2008 à 23:07
Salut john,

Je suis un peu fainéant, mais je travaille beucoup avec les nombres extended! Vu tous tes essais je ne sais plus trop ce que tu essayes!

Si tu as toujours des problèmes, peux-tu résumer ton problème:
entrées:
sorties:
Précision à chaque fois requise ou préciser si indifférente. 
Bien à toi

Jean_Jean
0
John Dogget Messages postés 384 Date d'inscription vendredi 18 juin 2004 Statut Membre Dernière intervention 7 mai 2009
15 déc. 2008 à 23:43
Boua laisses tomber
Je mets Frac() à la poubelle, et je me sers des chaînes de caracteres, ça marche dans 100% des cas que j'ai testé.

Vla mon code :

function DecimalToFractionSimple(NombreReel:Extended):TFraction;
var
  ChaineDecimale:String;
  IndexChaine:Integer;
  ChaineNumerateur:String;
begin
  ChaineDecimale:=FloatToStr(NombreReel);
  for IndexChaine:=0 to Length(ChaineDecimale)-1 do
    if ChaineDecimale[IndexChaine]=DecimalSeparator then
      break;
  ChaineNumerateur:=AnsiReplaceStr(ChaineDecimale,DecimalSeparator,'');
  Result.Numerateur:=StrToInt64(ChaineNumerateur);
  Result.Denominateur:=StrToInt64('1'+DupeString('0',Length(ChaineDecimale)-IndexChaine));
end;

C'est probablement pas très optimisé, mais ça marche bien, et en plus ça semble plus rapide que la methode mathematique.
0
Rejoignez-nous