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