Home | History | Annotate | Download | only in test
      1 """Tests for binary operators on subtypes of built-in types."""
      2 
      3 import unittest
      4 from test import test_support
      5 
      6 def gcd(a, b):
      7     """Greatest common divisor using Euclid's algorithm."""
      8     while a:
      9         a, b = b%a, a
     10     return b
     11 
     12 def isint(x):
     13     """Test whether an object is an instance of int or long."""
     14     return isinstance(x, int) or isinstance(x, long)
     15 
     16 def isnum(x):
     17     """Test whether an object is an instance of a built-in numeric type."""
     18     for T in int, long, float, complex:
     19         if isinstance(x, T):
     20             return 1
     21     return 0
     22 
     23 def isRat(x):
     24     """Test wheter an object is an instance of the Rat class."""
     25     return isinstance(x, Rat)
     26 
     27 class Rat(object):
     28 
     29     """Rational number implemented as a normalized pair of longs."""
     30 
     31     __slots__ = ['_Rat__num', '_Rat__den']
     32 
     33     def __init__(self, num=0L, den=1L):
     34         """Constructor: Rat([num[, den]]).
     35 
     36         The arguments must be ints or longs, and default to (0, 1)."""
     37         if not isint(num):
     38             raise TypeError, "Rat numerator must be int or long (%r)" % num
     39         if not isint(den):
     40             raise TypeError, "Rat denominator must be int or long (%r)" % den
     41         # But the zero is always on
     42         if den == 0:
     43             raise ZeroDivisionError, "zero denominator"
     44         g = gcd(den, num)
     45         self.__num = long(num//g)
     46         self.__den = long(den//g)
     47 
     48     def _get_num(self):
     49         """Accessor function for read-only 'num' attribute of Rat."""
     50         return self.__num
     51     num = property(_get_num, None)
     52 
     53     def _get_den(self):
     54         """Accessor function for read-only 'den' attribute of Rat."""
     55         return self.__den
     56     den = property(_get_den, None)
     57 
     58     def __repr__(self):
     59         """Convert a Rat to an string resembling a Rat constructor call."""
     60         return "Rat(%d, %d)" % (self.__num, self.__den)
     61 
     62     def __str__(self):
     63         """Convert a Rat to a string resembling a decimal numeric value."""
     64         return str(float(self))
     65 
     66     def __float__(self):
     67         """Convert a Rat to a float."""
     68         return self.__num*1.0/self.__den
     69 
     70     def __int__(self):
     71         """Convert a Rat to an int; self.den must be 1."""
     72         if self.__den == 1:
     73             try:
     74                 return int(self.__num)
     75             except OverflowError:
     76                 raise OverflowError, ("%s too large to convert to int" %
     77                                       repr(self))
     78         raise ValueError, "can't convert %s to int" % repr(self)
     79 
     80     def __long__(self):
     81         """Convert a Rat to an long; self.den must be 1."""
     82         if self.__den == 1:
     83             return long(self.__num)
     84         raise ValueError, "can't convert %s to long" % repr(self)
     85 
     86     def __add__(self, other):
     87         """Add two Rats, or a Rat and a number."""
     88         if isint(other):
     89             other = Rat(other)
     90         if isRat(other):
     91             return Rat(self.__num*other.__den + other.__num*self.__den,
     92                        self.__den*other.__den)
     93         if isnum(other):
     94             return float(self) + other
     95         return NotImplemented
     96 
     97     __radd__ = __add__
     98 
     99     def __sub__(self, other):
    100         """Subtract two Rats, or a Rat and a number."""
    101         if isint(other):
    102             other = Rat(other)
    103         if isRat(other):
    104             return Rat(self.__num*other.__den - other.__num*self.__den,
    105                        self.__den*other.__den)
    106         if isnum(other):
    107             return float(self) - other
    108         return NotImplemented
    109 
    110     def __rsub__(self, other):
    111         """Subtract two Rats, or a Rat and a number (reversed args)."""
    112         if isint(other):
    113             other = Rat(other)
    114         if isRat(other):
    115             return Rat(other.__num*self.__den - self.__num*other.__den,
    116                        self.__den*other.__den)
    117         if isnum(other):
    118             return other - float(self)
    119         return NotImplemented
    120 
    121     def __mul__(self, other):
    122         """Multiply two Rats, or a Rat and a number."""
    123         if isRat(other):
    124             return Rat(self.__num*other.__num, self.__den*other.__den)
    125         if isint(other):
    126             return Rat(self.__num*other, self.__den)
    127         if isnum(other):
    128             return float(self)*other
    129         return NotImplemented
    130 
    131     __rmul__ = __mul__
    132 
    133     def __truediv__(self, other):
    134         """Divide two Rats, or a Rat and a number."""
    135         if isRat(other):
    136             return Rat(self.__num*other.__den, self.__den*other.__num)
    137         if isint(other):
    138             return Rat(self.__num, self.__den*other)
    139         if isnum(other):
    140             return float(self) / other
    141         return NotImplemented
    142 
    143     __div__ = __truediv__
    144 
    145     def __rtruediv__(self, other):
    146         """Divide two Rats, or a Rat and a number (reversed args)."""
    147         if isRat(other):
    148             return Rat(other.__num*self.__den, other.__den*self.__num)
    149         if isint(other):
    150             return Rat(other*self.__den, self.__num)
    151         if isnum(other):
    152             return other / float(self)
    153         return NotImplemented
    154 
    155     __rdiv__ = __rtruediv__
    156 
    157     def __floordiv__(self, other):
    158         """Divide two Rats, returning the floored result."""
    159         if isint(other):
    160             other = Rat(other)
    161         elif not isRat(other):
    162             return NotImplemented
    163         x = self/other
    164         return x.__num // x.__den
    165 
    166     def __rfloordiv__(self, other):
    167         """Divide two Rats, returning the floored result (reversed args)."""
    168         x = other/self
    169         return x.__num // x.__den
    170 
    171     def __divmod__(self, other):
    172         """Divide two Rats, returning quotient and remainder."""
    173         if isint(other):
    174             other = Rat(other)
    175         elif not isRat(other):
    176             return NotImplemented
    177         x = self//other
    178         return (x, self - other * x)
    179 
    180     def __rdivmod__(self, other):
    181         """Divide two Rats, returning quotient and remainder (reversed args)."""
    182         if isint(other):
    183             other = Rat(other)
    184         elif not isRat(other):
    185             return NotImplemented
    186         return divmod(other, self)
    187 
    188     def __mod__(self, other):
    189         """Take one Rat modulo another."""
    190         return divmod(self, other)[1]
    191 
    192     def __rmod__(self, other):
    193         """Take one Rat modulo another (reversed args)."""
    194         return divmod(other, self)[1]
    195 
    196     def __eq__(self, other):
    197         """Compare two Rats for equality."""
    198         if isint(other):
    199             return self.__den == 1 and self.__num == other
    200         if isRat(other):
    201             return self.__num == other.__num and self.__den == other.__den
    202         if isnum(other):
    203             return float(self) == other
    204         return NotImplemented
    205 
    206     def __ne__(self, other):
    207         """Compare two Rats for inequality."""
    208         return not self == other
    209 
    210     # Silence Py3k warning
    211     __hash__ = None
    212 
    213 class RatTestCase(unittest.TestCase):
    214     """Unit tests for Rat class and its support utilities."""
    215 
    216     def test_gcd(self):
    217         self.assertEqual(gcd(10, 12), 2)
    218         self.assertEqual(gcd(10, 15), 5)
    219         self.assertEqual(gcd(10, 11), 1)
    220         self.assertEqual(gcd(100, 15), 5)
    221         self.assertEqual(gcd(-10, 2), -2)
    222         self.assertEqual(gcd(10, -2), 2)
    223         self.assertEqual(gcd(-10, -2), -2)
    224         for i in range(1, 20):
    225             for j in range(1, 20):
    226                 self.assertTrue(gcd(i, j) > 0)
    227                 self.assertTrue(gcd(-i, j) < 0)
    228                 self.assertTrue(gcd(i, -j) > 0)
    229                 self.assertTrue(gcd(-i, -j) < 0)
    230 
    231     def test_constructor(self):
    232         a = Rat(10, 15)
    233         self.assertEqual(a.num, 2)
    234         self.assertEqual(a.den, 3)
    235         a = Rat(10L, 15L)
    236         self.assertEqual(a.num, 2)
    237         self.assertEqual(a.den, 3)
    238         a = Rat(10, -15)
    239         self.assertEqual(a.num, -2)
    240         self.assertEqual(a.den, 3)
    241         a = Rat(-10, 15)
    242         self.assertEqual(a.num, -2)
    243         self.assertEqual(a.den, 3)
    244         a = Rat(-10, -15)
    245         self.assertEqual(a.num, 2)
    246         self.assertEqual(a.den, 3)
    247         a = Rat(7)
    248         self.assertEqual(a.num, 7)
    249         self.assertEqual(a.den, 1)
    250         try:
    251             a = Rat(1, 0)
    252         except ZeroDivisionError:
    253             pass
    254         else:
    255             self.fail("Rat(1, 0) didn't raise ZeroDivisionError")
    256         for bad in "0", 0.0, 0j, (), [], {}, None, Rat, unittest:
    257             try:
    258                 a = Rat(bad)
    259             except TypeError:
    260                 pass
    261             else:
    262                 self.fail("Rat(%r) didn't raise TypeError" % bad)
    263             try:
    264                 a = Rat(1, bad)
    265             except TypeError:
    266                 pass
    267             else:
    268                 self.fail("Rat(1, %r) didn't raise TypeError" % bad)
    269 
    270     def test_add(self):
    271         self.assertEqual(Rat(2, 3) + Rat(1, 3), 1)
    272         self.assertEqual(Rat(2, 3) + 1, Rat(5, 3))
    273         self.assertEqual(1 + Rat(2, 3), Rat(5, 3))
    274         self.assertEqual(1.0 + Rat(1, 2), 1.5)
    275         self.assertEqual(Rat(1, 2) + 1.0, 1.5)
    276 
    277     def test_sub(self):
    278         self.assertEqual(Rat(7, 2) - Rat(7, 5), Rat(21, 10))
    279         self.assertEqual(Rat(7, 5) - 1, Rat(2, 5))
    280         self.assertEqual(1 - Rat(3, 5), Rat(2, 5))
    281         self.assertEqual(Rat(3, 2) - 1.0, 0.5)
    282         self.assertEqual(1.0 - Rat(1, 2), 0.5)
    283 
    284     def test_mul(self):
    285         self.assertEqual(Rat(2, 3) * Rat(5, 7), Rat(10, 21))
    286         self.assertEqual(Rat(10, 3) * 3, 10)
    287         self.assertEqual(3 * Rat(10, 3), 10)
    288         self.assertEqual(Rat(10, 5) * 0.5, 1.0)
    289         self.assertEqual(0.5 * Rat(10, 5), 1.0)
    290 
    291     def test_div(self):
    292         self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3))
    293         self.assertEqual(Rat(10, 3) / 3, Rat(10, 9))
    294         self.assertEqual(2 / Rat(5), Rat(2, 5))
    295         self.assertEqual(3.0 * Rat(1, 2), 1.5)
    296         self.assertEqual(Rat(1, 2) * 3.0, 1.5)
    297 
    298     def test_floordiv(self):
    299         self.assertEqual(Rat(10) // Rat(4), 2)
    300         self.assertEqual(Rat(10, 3) // Rat(4, 3), 2)
    301         self.assertEqual(Rat(10) // 4, 2)
    302         self.assertEqual(10 // Rat(4), 2)
    303 
    304     def test_eq(self):
    305         self.assertEqual(Rat(10), Rat(20, 2))
    306         self.assertEqual(Rat(10), 10)
    307         self.assertEqual(10, Rat(10))
    308         self.assertEqual(Rat(10), 10.0)
    309         self.assertEqual(10.0, Rat(10))
    310 
    311     def test_future_div(self):
    312         exec future_test
    313 
    314     # XXX Ran out of steam; TO DO: divmod, div, future division
    315 
    316 future_test = """
    317 from __future__ import division
    318 self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3))
    319 self.assertEqual(Rat(10, 3) / 3, Rat(10, 9))
    320 self.assertEqual(2 / Rat(5), Rat(2, 5))
    321 self.assertEqual(3.0 * Rat(1, 2), 1.5)
    322 self.assertEqual(Rat(1, 2) * 3.0, 1.5)
    323 self.assertEqual(eval('1/2'), 0.5)
    324 """
    325 
    326 def test_main():
    327     test_support.run_unittest(RatTestCase)
    328 
    329 
    330 if __name__ == "__main__":
    331     test_main()
    332