1"""Tests for binary operators on subtypes of built-in types.""" 2 3import unittest 4from test import test_support 5 6def gcd(a, b): 7 """Greatest common divisor using Euclid's algorithm.""" 8 while a: 9 a, b = b%a, a 10 return b 11 12def isint(x): 13 """Test whether an object is an instance of int or long.""" 14 return isinstance(x, int) or isinstance(x, long) 15 16def 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 23def isRat(x): 24 """Test wheter an object is an instance of the Rat class.""" 25 return isinstance(x, Rat) 26 27class 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 a 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 a 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 213class 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 316future_test = """ 317from __future__ import division 318self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3)) 319self.assertEqual(Rat(10, 3) / 3, Rat(10, 9)) 320self.assertEqual(2 / Rat(5), Rat(2, 5)) 321self.assertEqual(3.0 * Rat(1, 2), 1.5) 322self.assertEqual(Rat(1, 2) * 3.0, 1.5) 323self.assertEqual(eval('1/2'), 0.5) 324""" 325 326def test_main(): 327 test_support.run_unittest(RatTestCase) 328 329 330if __name__ == "__main__": 331 test_main() 332