Search code examples
pythonclassfractions

How to return a negative fraction when subtracting with use of classes?


In part of my code I can subtract fractions, however if I enter (- p) where p is a fraction I get a TypeError: unsupported operand type(s) for +: "Fraction" and "Fraction"

def gcd(denominator, numerator):
    if numerator == 0:
        return denominator
    else:
        return gcd(numerator, (denominator % numerator))

class Fraction:
    
    def __init__(self,numerator = 0, denominator = 1):

        self.numerator = int(numerator / gcd(abs(denominator),abs(numerator) ))
        self.denominator = int(denominator / gcd(abs(denominator),abs(numerator) ))
        if self.denominator < 0:
            self.denominator = abs(self.denominator)
            self.numerator = -1*self.numerator
        elif self.denominator == 0:
            raise ZeroDivisionError
        
    def __str__(self):
        if self.denominator == 1:
            return str(self.numerator)
        else:
            return str(self.numerator) + "/" + str(self.denominator)
    
    def __rsub__(self,other):
        return self.__sub__(other)

    def __sub__(self,other):
        if type(other) == int:
            other = Fraction(other,1)
            return self.sub(other)
        else:
            
            return self.sub(other)
    
    def sub(self,other):
        result = Fraction()
        
        result.numerator = other.denominator * self.numerator - self.denominator * other.numerator
        result.denominator = other.denominator * self.denominator
        
        multiple = gcd(result.denominator,result.numerator)
        
        result.numerator = int(result.numerator / multiple)
        result.denominator = int(result.denominator / multiple)
        
        return result

p = Fraction(2,3)

r = (- p)

However, when my input is (1 - p) I get the correct output. Suppose p = Fraction(2, 3) then I would like (- p) to return (-2 / 3) or (2/ -3). The problem seems to me to be in the fact that no input is given for the first argument when subtracting. While searching I did come across things like __neg__, but I'm still new to python and using classes so I don't know exactly how to implement this. Does anyone know how I can fix this?

Thanks in advance!


Solution

  • You are right; you need to implement __neg__ because that minus is not the (binary) subtraction operator, but the unary minus.

    Here is how you can do it:

        def __neg__(self):
            return Fraction(-self.numerator, self.denominator)
    

    Other issues

    __rsub__

    You need to change the implementation of __rsub__, because that method will be called when other does not support __sub__. For instance, it will kick in when you evaluate this:

    p = 1 - Fraction(2, 3)
    

    That evaluation will not work as it currently stands. You need to have this:

        def __rsub__(self, other):
            return Fraction(other) - self
    

    or, explicitly calling __sub__:

        def __rsub__(self, other):
            return Fraction(other).__sub__(self)
    

    Normalising

    The constructor correctly normalises the fraction, making sure the denominator is positive, but you don't do the same in the sub method. There the result may not be normalised: the denominator could remain negative.

    Moreover, it is a pity that you duplicate the gcd-related code that is already in the constructor. It is better to rely on the constructor for that logic.

    Immutability

    It is better to treat instances as immutable. So you should not have any assignments to instance.numerator and instance.denominator outside of the constructor. Make sure to first determine the numerator and denominator (without normalisation), and then call the constructor passing these as arguments.

    Comparisons

    You may want to compare Fractions for equality or relative order. For that you can implement __eq__, __lt__, ...etc.

    Proposed code

    Here is how I would do it:

    def gcd(denominator, numerator):
        if numerator == 0:
            return denominator
        else:
            return gcd(numerator, denominator % numerator)
    
    class Fraction:
        def __new__(cls, numerator=0, denominator=1):
            if isinstance(numerator, Fraction):
                return numerator  # Use this instance and ignore 2nd argument
            return super(Fraction, cls).__new__(cls)
    
        def __init__(self, numerator=0, denominator=1):
            if isinstance(numerator, Fraction):
                return  # self is already initialised by __new__
            if denominator == 0:
                raise ZeroDivisionError
            div = gcd(abs(denominator), abs(numerator))
            if denominator < 0:
                div = -div
            self.numerator = numerator // div
            self.denominator = denominator // div
            
        def __str__(self):
            if self.denominator == 1:
                return str(self.numerator)
            else:
                return f"{self.numerator}/{self.denominator}"
        
        def __rsub__(self, other):
            return Fraction(other) - self
    
        def __sub__(self, other):
            other = Fraction(other)
            return Fraction(other.denominator * self.numerator 
                                 - self.denominator * other.numerator,
                            other.denominator * self.denominator)
        
        def __neg__(self):
            return Fraction(-self.numerator, self.denominator)
    
        def __eq__(self, other):
            other = Fraction(other)
            return (self.numerator == other.numerator and
                    self.denominator == other.denominator)
    
        def __lt__(self, other):
            other = Fraction(other)
            return (other.denominator * self.numerator <
                         self.denominator * other.numerator)
    
        def __gt__(self, other):
            return Fraction(other) < self
    
        def __le__(self, other):
            return self < other or self == other
    
        def __ge__(self, other):
            return self > other or self == other