John Dogget
Messages postés384Date d'inscriptionvendredi 18 juin 2004StatutMembreDernière intervention 7 mai 2009
-
7 déc. 2008 à 17:31
John Dogget
Messages postés384Date d'inscriptionvendredi 18 juin 2004StatutMembreDerniè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.
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;
John Dogget
Messages postés384Date d'inscriptionvendredi 18 juin 2004StatutMembreDerniè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)
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 .
John Dogget
Messages postés384Date d'inscriptionvendredi 18 juin 2004StatutMembreDerniè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;
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.
John Dogget
Messages postés384Date d'inscriptionvendredi 18 juin 2004StatutMembreDerniè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
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
cs_Jean_Jean
Messages postés615Date d'inscriptiondimanche 13 août 2006StatutMembreDernière intervention13 décembre 20183 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
cs_Jean_Jean
Messages postés615Date d'inscriptiondimanche 13 août 2006StatutMembreDernière intervention13 décembre 20183 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
John Dogget
Messages postés384Date d'inscriptionvendredi 18 juin 2004StatutMembreDerniè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 ).
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
cs_Jean_Jean
Messages postés615Date d'inscriptiondimanche 13 août 2006StatutMembreDernière intervention13 décembre 20183 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!
cs_Jean_Jean
Messages postés615Date d'inscriptiondimanche 13 août 2006StatutMembreDernière intervention13 décembre 20183 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
John Dogget
Messages postés384Date d'inscriptionvendredi 18 juin 2004StatutMembreDerniè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.