from fractions import gcd # A rational-number class, illustrating all the Python bells and whistles. # For rational numbers a and b: # * a.numer and a.denom are numerator and denominator in lowest terms. # They properties, meaning that they call methods to actually do the work # * a.numer and a.denom are *assignable*, assigning to either adjusts the # the result to be in lowest terms. # * Rationals print in the familiar x/y format. # * Rational arithmetic operators a+b, a-b, a*b, a / b, -a may be used. # They are implemented by __add__, etc. # * Arithmetic also works when one operand is an int. # * Comparisons a <= b, a >= b, a < b, etc. work. They are implemented # by the methods __le__, __ge__, etc. # * A rational value of 0 is a false value; all others are true (similarly # to ints.) # * float(a) converts to floating point (the __float__ method). class rational: def __init__(self, num, den): g = gcd(num, den) self._numer = num // g self._denom = den // g @property def numer(self): return self._numer @numer.setter def numer(self, v): self._numer = v / gcd(v, self._denom) @property def denom(self): return self._denom @denom.setter def denom(self, v): self._denom = v / gcd(self._numer, v) # Arithmetic def __add__(self, x): """ + and + .""" # getattr(x, 'numer', x) will return x.numer when x is rational. # It will return the default (x) when x is an integer. # As a result, whenever x is rational *or* integer, n1 and d1 are its # numerator and denominator. n1 = getattr(x, 'numer', x) d1 = getattr(x, 'denom', 1) return rational(self._numer * d1 + n1 * self._denom, self._denom * d1) # The __r...__ methods are called by the standard __add__ methods on # numbers wheh the second argument is not a standard numeric type. def __radd__(self, x): """For + .""" return rational(x * self._denom + self._numer, self._denom) def __sub__(self, x): """ - and - .""" n1 = getattr(x, 'numer', x) d1 = getattr(x, 'denom', 1) return rational(self._numer * d1 - n1 * self._denom, self._denom * d1) def __rsub__(self, x): """ - """ return rational(x * self._denom - self._numer, self._denom) def __mul__(self, x): """ * and * """ n1 = getattr(x, 'numer', x) d1 = getattr(x, 'denom', 1) return rational(self._numer * n1, self._denom * d1) def __rmul__(self, x): """ * and * """ return rational(self._numer * x, self._denom) def __truediv__(self, x): """ / and / """ n1 = getattr(x, 'numer', x) d1 = getattr(x, 'denom', 1) return rational(self._numer * d1, self._denom * n1) def __rtruediv__(self, x): """ / and / """ return rational(self._denom * x, self._numer) def __truediv__(self, x): """ / and / """ n1 = getattr(x, 'numer', x) d1 = getattr(x, 'denom', 1) return rational(self._numer * d1, self._denom * n1) def __rtruediv__(self, x): """ / and / """ return rational(self._denom * x, self._numer) def __pow__(self, x): """ ** """ return rational(self._numer ** x, self._denom ** x) def __neg__(self): """-""" return rational(-self._numer, self._denom) def __float__(self): """Convert to floating point.""" return self._numer / self._denom # Comparisons def __eq__(self, x): """self == x""" n1 = getattr(x, 'numer', x) d1 = getattr(x, 'denom', 1) return n1 == self._numer and d1 == self.denom def __le__(self, x): """self <= x""" n1 = getattr(x, 'numer', x) d1 = getattr(x, 'denom', 1) return self._numer * d1 <= n1 * self._denom def __lt__(self, x): """self < x""" n1 = getattr(x, 'numer', x) d1 = getattr(x, 'denom', 1) return self._numer * d1 < n1 * self._denom def __ge__(self, x): """self >= x""" n1 = getattr(x, 'numer', x) d1 = getattr(x, 'denom', 1) return self._numer * d1 >= n1 * self._denom def __gt__(self, x): """self > x""" n1 = getattr(x, 'numer', x) d1 = getattr(x, 'denom', 1) return self._numer * d1 > n1 * self._denom # Truth values def __bool__(self): return self._numer != 0 # String representations def __str__(self): if self.numer == 0: return "0" elif self.denom == 1: return str(self.numer) else: return "{0}/{1}".format(self.numer, self.denom) def __repr__(self): return "rational({}, {})".format(self.numer, self.denom)