Fractions en Python

a marqué ce sujet comme résolu.

Bonjour,

Je suis en train d’essayer de développer une classe permettant de manipuler des fractions en Python (je sais qu’il existe déjà une bibliothèque à cette effet, mais elle ne me convient pas car elle ne donne pas la fraction irréductible).

J’ai établi le code suivant (qui fonctionne très bien avec les nombres décimaux) :

class Fraction:
    def __init__(self,value):
        self.numerator = value
        self.denominator = 1
                
        while not self.numerator == int(self.numerator):
            self.numerator *= 10
            self.denominator *= 10
        self.numerator = int(self.numerator)
        gcd = np.gcd(self.numerator,self.denominator)
        self.numerator//= gcd
        self.denominator //= gcd
    def __repr__(self):
        return f"{self.numerator}/{self.denominator}"

    def __add__(self,fraction):
        lcm = np.lcm(self.denominator,fraction.denominator)
        denominator = lcm
        num_1 = self.numerator * (lcm//self.denominator)
        num_2 = fraction.numerator * (lcm//fraction.denominator)

        return Fraction((num_1 + num_2)/denominator)
    def __sub__(self,fraction):
        fraction.numerator *= -1
        return self.__add__(fraction)
    def __mul__(self,fraction):
        n = self.numerator * fraction.numerator
        print(self.numerator,fraction.numerator,n)
        d = self.denominator * fraction.denominator
        print(self.numerator,fraction.numerator,n)
        return Fraction(n/d)
    def __truediv__(self,fraction):
        fraction.numerator, fraction.denominator = fraction.denominator, fraction.numerator
        print(fraction)
        return  self.__mul__(fraction)

Seulement voilà, tout commence à capoter dès qu’on lui donne des rationnels non décimaux(comme 1/3, par exemple).

J’ai bien vu que le problème vient de la méthode que j’utilise dans init pour convertir la valeur décimale en une fraction irréductible, mais je ne vois pas comment m’y prendre pour qu’elle prenne en charge aussi les rationnels non décimaux…

Pourriez-vous m’aider ?

Tu lui donnes une valeur, qui est un nombre à virgule. Donc un nombre parfois égal au nombre que tu crois lui donner, mais généralement, tu lui donnes un nombre légèrement différent du nombre que tu voudrais lui donner.

Ici, tu veux lui donner 1/3, tu lui donnes en fait un truc comme 0.333333333333333333333333625 ; J’ai mis 25 à la fin, ce n’est pas par hasard.

Et aucune fonction ne pourra deviner le nombre que tu voulais lui donner.

Bonjour,

Comme les nombres rationnels non-décimaux ont pour caractéristique d’avoir une partie décimale périodique, je me disais que je pourrais essayer de me baser sur ça pour les reconnaître et derrière essayer de les traiter… Reste à savoir comment faire pour les traiter !

Merci beaucoup,

@flopy78

Salut,

Ton code marche en fait comme attendu. C’est inattendu pour toi, parce que tu te fais surprendre par le fonctionnement des nombres flottants.

Par exemple, en affichant plus de décimales, on voit mieux ce qu’il se passe :

>>> print(f"{1/3:.30f}")
0.333333333333333314829616256247

Le flottant qu’on obtient en évaluant 1/3 en Python n’est pas égal au nombre rationnel 1/3.

La représentation de ce flottant sous forme de fraction n’est pas 1/3, mais un truc du genre 8333333333333331/25000000000000000 d’après ton programme, ce qui ne me choque pas plus que ça.

Sur plein d’autres entrées, le même problème passe inaperçu grâce aux arrondis :

>>> print(f"{0.2:.30f}")
0.200000000000000011102230246252

Quand on prend le flottant le plus proche de 0.2 (il y a une erreur d’arrondi fait à la conversion) multiplié par 10 (qui est représentable exactement par un flottant) tombe exactement sur 2 (l’erreur d’arrondi est nulle, c’est un cas particulier).

>>> print(f"{0.2*10:.30f}")
2.000000000000000000000000000000

Bref, si tu veux tout savoir, j’ai écrit un tuto sur le sujet.


Maintenant, pour résoudre ton problème, il faudrait lire une représentation où il n’y a pas d’erreur d’arrondi. La bibliothèque standard de Python propose de lire des rationnels à partir d’une chaîne de caractère. Par exemple, on met la chaîne "1/3" comme argument et pas le flottant résultant de 1/3

Salut,

La représentation de ce flottant sous forme de fraction n’est pas 1/3, mais un truc du genre 8333333333333331/25000000000000000 d’après ton programme, ce qui ne me choque pas plus que ça.

Note qu’on a pas besoin de bricoler en multipliant plein de fois par 10 comme tu le fais dans ton script pour trouver la fraction qui est effectivement représentée par un nombre flottant.

>>> (1/3).as_integer_ratio()
(6004799503160661, 18014398509481984)

Qui est une meilleur approximation de 1/3 que la valeur trouvée par le script… Les divisions entières sur les flottants couplés à des multiplications par 10 doivent perdre en précision quelque part, j’imagine (comme multiplier un flottant par 10 n’est pas nécessairement exact…).

Par ailleurs,

je sais qu’il existe déjà une bibliothèque à cette effet, mais elle ne me convient pas car elle ne donne pas la fraction irréductible.

Heu, tu es sûr de ton coup? fractions.Fraction ferait très bien l’affaire, et les problèmes de précision lorsqu’on tente de convertir un flottant sont même discutées…

+1 -0

Et pour construire de telles fractions il serait plus simple d’utiliser les opérations que tu as toi-même implémentées. Donc pour représenter ⅓ il te suffit d’écrire Fraction(1) / Fraction(3).

entwanne

Mouais, le mieux est encore de pouvoir construire une Fraction en lui filant deux nombres directement, comme dans la lib standard. Par ailleurs, l’implémentation de Fraction.__mul__ faite par l’OP est incorrecte parce qu’elle passe aussi par un flottant, donc Fraction(1) / Fraction(3) donne la même chose que Fraction(1/3).

Le petit problème est que je veux pouvoir afficher (avec __repr__) ma fraction sous forme irréductible, ce que je ne peux pas faire (enfin, je pense) avec fractions.Fraction.

__repr__ n’est pas la bonne méthode pour ça, __repr__ est censé donné une représentation textuelle relativement univoque de l’objet représenté (à des fins de debugging) plutôt que quelque chose destiné pour la lecture par l’utilisateur final. Il y a __str__ à cette fin (appelée par str(object), et aussi par print par exemple), et bien évidemment fractions.Fraction l’implémente correctement :

>>> from fractions import Fraction
>>> Fraction(1, 3)
Fraction(1, 3)
>>> str(_)
'1/3'
>>> print(Fraction(1, 3))
1/3
+0 -0

Bonjour, Il me semble que le module fractions remplis tes demandes ou je n’ai d’avoir bien compris le problème:

Fraction affiche toujours le résultat sous forme irréductible:

>>> from fractions import Fraction
>>> a = Fraction(12,18)
>>> print(a)
2/3
>>> print(repr(a))
Fraction(2, 3)

>>> b = Fraction(1/3)
# donne une fraction irréductible: Fraction(6004799503160661, 18014398509481984)
# les mêmes type d'erreurs que ta classe à cause la conversion des floats

Après, Fraction(1) / Fraction(3) exclue toute possibilité de prendre une fraction en entrée…

Salut, la fraction que tu prends en entrée elle viendrait d’où ? Si elle est dans le code, ben c’est pas une fraction que tu prends en entrée mais un flottant, donc t’auras tous les problèmes décrits dans les messages ci-dessus (et il n’y a pas de manière de régler tout ça, t’auras forcément une perte de précision quelque part). Mais si elle vient par exemple d’une entrée utilisateur, ben tu peux parser l’entrée pour récupérer le numérateur et le dénominateur de la fraction et créer une Fraction en les utilisant.

+2 -0

En fait, dans la question initiale, la partie intéressante était la partie entre parenthèses : je sais qu’il existe déjà une bibliothèque à cet effet, mais elle ne me convient pas car elle ne donne pas la fraction irréductible).

Les bibliothèques mises à disposition sont globalement bien faites, et complètes. Soit ton besoin est ultra-spécifique, et ok, il va falloir le coder toi-même, en utilisant la bibliothèque mise à disposition, et en ajoutant telle ou telle méthode. Soit ton besoin est relativement standard, et il a forcément été prévu et déjà développé. Ici, ton besoin est ultra-classique.

A titre d’exercice, on peut s’amuser à réinventer la roue. Pourquoi pas, c’est formateur. Mais il faut être conscient que tu réinventes la roue. Tu refais un truc qui a déjà été fait et testé des milliers de fois.

Afficher une fraction sous la forme 1/3, ce n’est pas un besoin très original, ça n’a pas de sens de redévelopper toutes les fonctions addition, multiplication, etc, sous prétexte qu’on ne trouve pas comment afficher une fraction sous sa forme irréductible.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte