Search code examples
pythonpython-3.xfractions

Incorrect fraction division in python


I'm doing a tutorial on algorithms

One of the exercises is to create a fraction class that can take a negative denominator and still correctly present the results. However, when I'm dividing I'm still getting negative output:

class Fraction:
    def __init__(self, num, denom):
        if isinstance(num, int) or isinstance(denom, int):
            common = gcd(num, abs(denom))
            self._num = num//common
            self._denom = abs(denom)//common
        else:
            raise TypeError

    def __str__(self):
        return "%d / %d" % (self._num, self._denom)

    def __mul__(self, other):
        denum = self._num * other._num
        div = self._denom * other._denom
        return Fraction(denum, div)

    def __truediv__(self, other):
        temp_fraction = Fraction(other._denom, other._num)
        return self.__mul__(temp_fraction)

def gcd(a, b):
    while(b):
        a, b = b, a%b
    return a

if __name__ == '__main__':
    print(Fraction(-4, -5) / Fraction(-1, -2)) 
    print(Fraction(-4, 5) / Fraction(-1, 2))
    # Both output -8 / 5, should be 8 / 5

Can someone tell me what I'm doing wrong?

Edit I've corrected my code and rewrote some erroneous assertions in my doctest. All test pass now!

Full code:

from fractions import gcd

class Fraction:
    """
    Checking if instantiating a fraction works
    >>> print(Fraction(1, 2))
    1 / 2
    >>> print(Fraction(-1, 2))
    -1 / 2
    >>> print(Fraction('Foo', 'Bar'))
    Traceback (most recent call last):
    TypeError

    Adding a fraction to another fraction
    >>> print(Fraction(1, 2) + Fraction(1, 4))
    3 / 4
    >>> print(Fraction(-1, 2) + Fraction(2, 2))
    1 / 2
    >>> print(Fraction(-1, 5) + Fraction(-1, 5))
    -2 / 5
    >>> print(Fraction(-1, -5) + Fraction(-1, -5))
    2 / 5

    Substracting a fraction from another
    >>> print(Fraction(3, 4) - Fraction(1, 4))
    1 / 2
    >>> print(Fraction(-1, 2) - Fraction(1, 4))
    -3 / 4
    >>> print(Fraction(-2, 10) - Fraction(-1, 10))
    -1 / 10
    >>> print(Fraction(-2, -10) - Fraction(-1, -10))
    1 / 10

    Multiplying 2 fractions
    >>> print(Fraction(1, 5) * Fraction(1, 2))
    1 / 10
    >>> print(Fraction(-1, 2) * Fraction(1, 4))
    -1 / 8
    >>> print(Fraction(-1, 2) * Fraction(-1, 8))
    1 / 16
    >>> print(Fraction(-1, -2) * Fraction(-1, -8))
    1 / 16

    Dividing 2 fractions
    >>> print(Fraction(1, 4) / Fraction(1, 2))
    1 / 2
    >>> print(Fraction(-1, 2) / Fraction(1, 15))
    -15 / 2
    >>> print(Fraction(-4, 5) / Fraction(-1, 2))
    8 / 5
    >>> print(Fraction(-4, -5) / Fraction(-1, -2))
    8 / 5

    Equality between fractions
    >>> print(Fraction(1, 2) == Fraction(2, 4))
    True
    >>> print(Fraction(1, 2) == Fraction(1, 3))
    False
    >>> print(Fraction(-2, 4) == Fraction(-1, 2))
    True
    >>> print(Fraction(-2, -4) == Fraction(-1, -2))
    True

    Non-equality between fractions
    >>> print(Fraction(1, 2) != Fraction(64, 128))
    False
    >>> print(Fraction(1, 4) != Fraction(999, 4000))
    True
    >>> print(Fraction(-3, 4) != Fraction(-3, 5))
    True
    >>> print(Fraction(-3, -4) != Fraction(-3, -5))
    True

    Larger size difference between fractions
    >>> print(Fraction(1, 2) > Fraction(857, 1713))
    False
    >>> print(Fraction(1, 2) > Fraction(857, 1715))
    True
    >>> print(Fraction(1, 338) >= Fraction(2, 676))
    True
    >>> print(Fraction(-2, 5) > Fraction(-1, 5))
    False
    >>> print(Fraction(-2, -5) > Fraction(-1, -5))
    True

    Smaller size difference between fractions
    >>> print(Fraction(1, 2) < Fraction(857, 1713))
    True
    >>> print(Fraction(1, 2) < Fraction(857, 1715))
    False
    >>> print(Fraction(1, 338) <= Fraction(2, 676))
    True
    >>> print(Fraction(-3, 7) < Fraction(-6, 7))
    False
    >>> print(Fraction(-3, -7) < Fraction(-6, -7))
    True
    """
    def __init__(self, num, denom):
        if isinstance(num, int) and isinstance(denom, int):
            common = gcd(num, denom)
            self._num = num//common
            self._denom = denom//common
        else:
            raise TypeError

    def __str__(self):
        return "%d / %d" % (self._num, self._denom)

    def __add__(self, other):
        denum = (self._num * other.get_denom()) + (other.get_num() * self._denom)
        div = (self._denom * other.get_denom())
        return Fraction(denum, div)

    def __sub__(self, other):
        denum = (self._num * other.get_denom()) - (other.get_num() * self._denom)
        div = (self._denom * other.get_denom())
        return Fraction(denum, div)

    def __mul__(self, other):
        denum = self._num * other.get_num()
        div = self._denom * other.get_denom()
        return Fraction(denum, div)

    def __truediv__(self, other):
        temp_fraction = Fraction(other.get_denom(), other.get_num())
        return self.__mul__(temp_fraction)

    def eq_denum(self, other):
        first = self._num * other.get_denom()
        last = other.get_num() * self._denom
        return first, last

    def __eq__(self, other):
        first, last = self.eq_denum(other)
        return first == last

    def __ne__(self, other):
        first, last = self.eq_denum(other)
        return first != last

    def __gt__(self, other):
        first, last = self.eq_denum(other)
        return first > last

    def __ge__(self, other):
        first, last = self.eq_denum(other)
        return first >= last

    def __lt__(self, other):
        first, last = self.eq_denum(other)
        return first < last

    def __le__(self, other):
        first, last = self.eq_denum(other)
        return first <= last

    def get_num(self):
        return self._num

    def get_denom(self):
        return self._denom


if __name__ == '__main__':
    import doctest
    doctest.testmod()

Solution

  • The problem is not with division but with how you use your gcd function in __init__.

    It seems you just have to remove all those abs when you create the fraction. This way, the gcd will be negative if denom is negative, and you get the desired behaviour.

    if isinstance(num, int) and isinstance(denom, int):
        common = gcd(num, denom)
        self._num = num//common
        self._denom = denom//common
    

    Some examples:

                                # with abs    # without abs
    print(Fraction( 4,  6))     #  2/3           2/3
    print(Fraction( 4, -6))     #  2/3          -2/3
    print(Fraction(-4,  6))     # -2/3          -2/3
    print(Fraction(-4, -6))     # -2/3           2/3
    

    Also, note that both numbers should be ints, not just one of them (use and).