'''\
This module implements rational numbers.

The entry point of this module is the function
        rat(numerator, denominator)
If either numerator or denominator is of an integral or rational type,
the result is a rational number, else, the result is the simplest of
the types float and complex which can hold numerator/denominator.
If denominator is omitted, it defaults to 1.
Rational numbers can be used in calculations with any other numeric
type.  The result of the calculation will be rational if possible.

There is also a test function with calling sequence
        test()
The documentation string of the test function contains the expected
output.
'''

# Contributed by Sjoerd Mullender

from types import *

def gcd(a, b):
    '''Calculate the Greatest Common Divisor.'''
    while b:
        a, b = b, a%b
    return a

def rat(num, den = 1):
    # must check complex before float
    if isinstance(num, complex) or isinstance(den, complex):
        # numerator or denominator is complex: return a complex
        return complex(num) / complex(den)
    if isinstance(num, float) or isinstance(den, float):
        # numerator or denominator is float: return a float
        return float(num) / float(den)
    # otherwise return a rational
    return Rat(num, den)

class Rat:
    '''This class implements rational numbers.'''

    def __init__(self, num, den = 1):
        if den == 0:
            raise ZeroDivisionError, 'rat(x, 0)'

        # normalize

        # must check complex before float
        if (isinstance(num, complex) or
            isinstance(den, complex)):
            # numerator or denominator is complex:
            # normalized form has denominator == 1+0j
            self.__num = complex(num) / complex(den)
            self.__den = complex(1)
            return
        if isinstance(num, float) or isinstance(den, float):
            # numerator or denominator is float:
            # normalized form has denominator == 1.0
            self.__num = float(num) / float(den)
            self.__den = 1.0
            return
        if (isinstance(num, self.__class__) or
            isinstance(den, self.__class__)):
            # numerator or denominator is rational
            new = num / den
            if not isinstance(new, self.__class__):
                self.__num = new
                if isinstance(new, complex):
                    self.__den = complex(1)
                else:
                    self.__den = 1.0
            else:
                self.__num = new.__num
                self.__den = new.__den
        else:
            # make sure numerator and denominator don't
            # have common factors
            # this also makes sure that denominator > 0
            g = gcd(num, den)
            self.__num = num / g
            self.__den = den / g
        # try making numerator and denominator of IntType if they fit
        try:
            numi = int(self.__num)
            deni = int(self.__den)
        except (OverflowError, TypeError):
            pass
        else:
            if self.__num == numi and self.__den == deni:
                self.__num = numi
                self.__den = deni

    def __repr__(self):
        return 'Rat(%s,%s)' % (self.__num, self.__den)

    def __str__(self):
        if self.__den == 1:
            return str(self.__num)
        else:
            return '(%s/%s)' % (str(self.__num), str(self.__den))

    # a + b
    def __add__(a, b):
        try:
            return rat(a.__num * b.__den + b.__num * a.__den,
                       a.__den * b.__den)
        except OverflowError:
            return rat(long(a.__num) * long(b.__den) +
                       long(b.__num) * long(a.__den),
                       long(a.__den) * long(b.__den))

    def __radd__(b, a):
        return Rat(a) + b

    # a - b
    def __sub__(a, b):
        try:
            return rat(a.__num * b.__den - b.__num * a.__den,
                       a.__den * b.__den)
        except OverflowError:
            return rat(long(a.__num) * long(b.__den) -
                       long(b.__num) * long(a.__den),
                       long(a.__den) * long(b.__den))

    def __rsub__(b, a):
        return Rat(a) - b

    # a * b
    def __mul__(a, b):
        try:
            return rat(a.__num * b.__num, a.__den * b.__den)
        except OverflowError:
            return rat(long(a.__num) * long(b.__num),
                       long(a.__den) * long(b.__den))

    def __rmul__(b, a):
        return Rat(a) * b

    # a / b
    def __div__(a, b):
        try:
            return rat(a.__num * b.__den, a.__den * b.__num)
        except OverflowError:
            return rat(long(a.__num) * long(b.__den),
                       long(a.__den) * long(b.__num))

    def __rdiv__(b, a):
        return Rat(a) / b

    # a % b
    def __mod__(a, b):
        div = a / b
        try:
            div = int(div)
        except OverflowError:
            div = long(div)
        return a - b * div

    def __rmod__(b, a):
        return Rat(a) % b

    # a ** b
    def __pow__(a, b):
        if b.__den != 1:
            if isinstance(a.__num, complex):
                a = complex(a)
            else:
                a = float(a)
            if isinstance(b.__num, complex):
                b = complex(b)
            else:
                b = float(b)
            return a ** b
        try:
            return rat(a.__num ** b.__num, a.__den ** b.__num)
        except OverflowError:
            return rat(long(a.__num) ** b.__num,
                       long(a.__den) ** b.__num)

    def __rpow__(b, a):
        return Rat(a) ** b

    # -a
    def __neg__(a):
        try:
            return rat(-a.__num, a.__den)
        except OverflowError:
            # a.__num == sys.maxint
            return rat(-long(a.__num), a.__den)

    # abs(a)
    def __abs__(a):
        return rat(abs(a.__num), a.__den)

    # int(a)
    def __int__(a):
        return int(a.__num / a.__den)

    # long(a)
    def __long__(a):
        return long(a.__num) / long(a.__den)

    # float(a)
    def __float__(a):
        return float(a.__num) / float(a.__den)

    # complex(a)
    def __complex__(a):
        return complex(a.__num) / complex(a.__den)

    # cmp(a,b)
    def __cmp__(a, b):
        diff = Rat(a - b)
        if diff.__num < 0:
            return -1
        elif diff.__num > 0:
            return 1
        else:
            return 0

    def __rcmp__(b, a):
        return cmp(Rat(a), b)

    # a != 0
    def __nonzero__(a):
        return a.__num != 0

    # coercion
    def __coerce__(a, b):
        return a, Rat(b)

def test():
    '''\
    Test function for rat module.

    The expected output is (module some differences in floating
    precission):
    -1
    -1
    0 0L 0.1 (0.1+0j)
    [Rat(1,2), Rat(-3,10), Rat(1,25), Rat(1,4)]
    [Rat(-3,10), Rat(1,25), Rat(1,4), Rat(1,2)]
    0
    (11/10)
    (11/10)
    1.1
    OK
    2 1.5 (3/2) (1.5+1.5j) (15707963/5000000)
    2 2 2.0 (2+0j)

    4 0 4 1 4 0
    3.5 0.5 3.0 1.33333333333 2.82842712475 1
    (7/2) (1/2) 3 (4/3) 2.82842712475 1
    (3.5+1.5j) (0.5-1.5j) (3+3j) (0.666666666667-0.666666666667j) (1.43248815986+2.43884761145j) 1
    1.5 1 1.5 (1.5+0j)

    3.5 -0.5 3.0 0.75 2.25 -1
    3.0 0.0 2.25 1.0 1.83711730709 0
    3.0 0.0 2.25 1.0 1.83711730709 1
    (3+1.5j) -1.5j (2.25+2.25j) (0.5-0.5j) (1.50768393746+1.04970907623j) -1
    (3/2) 1 1.5 (1.5+0j)

    (7/2) (-1/2) 3 (3/4) (9/4) -1
    3.0 0.0 2.25 1.0 1.83711730709 -1
    3 0 (9/4) 1 1.83711730709 0
    (3+1.5j) -1.5j (2.25+2.25j) (0.5-0.5j) (1.50768393746+1.04970907623j) -1
    (1.5+1.5j) (1.5+1.5j)

    (3.5+1.5j) (-0.5+1.5j) (3+3j) (0.75+0.75j) 4.5j -1
    (3+1.5j) 1.5j (2.25+2.25j) (1+1j) (1.18235814075+2.85446505899j) 1
    (3+1.5j) 1.5j (2.25+2.25j) (1+1j) (1.18235814075+2.85446505899j) 1
    (3+3j) 0j 4.5j (1+0j) (-0.638110484918+0.705394566962j) 0
    '''
    print rat(-1L, 1)
    print rat(1, -1)
    a = rat(1, 10)
    print int(a), long(a), float(a), complex(a)
    b = rat(2, 5)
    l = [a+b, a-b, a*b, a/b]
    print l
    l.sort()
    print l
    print rat(0, 1)
    print a+1
    print a+1L
    print a+1.0
    try:
        print rat(1, 0)
        raise SystemError, 'should have been ZeroDivisionError'
    except ZeroDivisionError:
        print 'OK'
    print rat(2), rat(1.5), rat(3, 2), rat(1.5+1.5j), rat(31415926,10000000)
    list = [2, 1.5, rat(3,2), 1.5+1.5j]
    for i in list:
        print i,
        if not isinstance(i, complex):
            print int(i), float(i),
        print complex(i)
        print
        for j in list:
            print i + j, i - j, i * j, i / j, i ** j,
            if not (isinstance(i, complex) or
                    isinstance(j, complex)):
                print cmp(i, j)
            print


if __name__ == '__main__':
    test()
