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 | |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 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) |
Guido van Rossum | 54e54c6 | 2001-09-04 19:14:14 +0000 | [diff] [blame^] | 45 | self.__num = long(num//g) |
| 46 | self.__den = long(den//g) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 47 | |
| 48 | def _get_num(self): |
| 49 | """Accessor function for read-only 'num' attribute of Rat.""" |
| 50 | return self.__num |
| 51 | num = getset(_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 = getset(_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 | class RatTestCase(unittest.TestCase): |
| 211 | """Unit tests for Rat class and its support utilities.""" |
| 212 | |
| 213 | def test_gcd(self): |
| 214 | self.assertEqual(gcd(10, 12), 2) |
| 215 | self.assertEqual(gcd(10, 15), 5) |
| 216 | self.assertEqual(gcd(10, 11), 1) |
| 217 | self.assertEqual(gcd(100, 15), 5) |
| 218 | self.assertEqual(gcd(-10, 2), -2) |
| 219 | self.assertEqual(gcd(10, -2), 2) |
| 220 | self.assertEqual(gcd(-10, -2), -2) |
| 221 | for i in range(1, 20): |
| 222 | for j in range(1, 20): |
| 223 | self.assert_(gcd(i, j) > 0) |
| 224 | self.assert_(gcd(-i, j) < 0) |
| 225 | self.assert_(gcd(i, -j) > 0) |
| 226 | self.assert_(gcd(-i, -j) < 0) |
| 227 | |
| 228 | def test_constructor(self): |
| 229 | a = Rat(10, 15) |
| 230 | self.assertEqual(a.num, 2) |
| 231 | self.assertEqual(a.den, 3) |
| 232 | a = Rat(10L, 15L) |
| 233 | self.assertEqual(a.num, 2) |
| 234 | self.assertEqual(a.den, 3) |
| 235 | a = Rat(10, -15) |
| 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(7) |
| 245 | self.assertEqual(a.num, 7) |
| 246 | self.assertEqual(a.den, 1) |
| 247 | try: |
| 248 | a = Rat(1, 0) |
| 249 | except ZeroDivisionError: |
| 250 | pass |
| 251 | else: |
| 252 | self.fail("Rat(1, 0) didn't raise ZeroDivisionError") |
| 253 | for bad in "0", 0.0, 0j, (), [], {}, None, Rat, unittest: |
| 254 | try: |
| 255 | a = Rat(bad) |
| 256 | except TypeError: |
| 257 | pass |
| 258 | else: |
| 259 | self.fail("Rat(%r) didn't raise TypeError" % bad) |
| 260 | try: |
| 261 | a = Rat(1, bad) |
| 262 | except TypeError: |
| 263 | pass |
| 264 | else: |
| 265 | self.fail("Rat(1, %r) didn't raise TypeError" % bad) |
| 266 | |
| 267 | def test_add(self): |
| 268 | self.assertEqual(Rat(2, 3) + Rat(1, 3), 1) |
| 269 | self.assertEqual(Rat(2, 3) + 1, Rat(5, 3)) |
| 270 | self.assertEqual(1 + Rat(2, 3), Rat(5, 3)) |
| 271 | self.assertEqual(1.0 + Rat(1, 2), 1.5) |
| 272 | self.assertEqual(Rat(1, 2) + 1.0, 1.5) |
| 273 | |
| 274 | def test_sub(self): |
| 275 | self.assertEqual(Rat(7, 2) - Rat(7, 5), Rat(21, 10)) |
| 276 | self.assertEqual(Rat(7, 5) - 1, Rat(2, 5)) |
| 277 | self.assertEqual(1 - Rat(3, 5), Rat(2, 5)) |
| 278 | self.assertEqual(Rat(3, 2) - 1.0, 0.5) |
| 279 | self.assertEqual(1.0 - Rat(1, 2), 0.5) |
| 280 | |
| 281 | def test_mul(self): |
| 282 | self.assertEqual(Rat(2, 3) * Rat(5, 7), Rat(10, 21)) |
| 283 | self.assertEqual(Rat(10, 3) * 3, 10) |
| 284 | self.assertEqual(3 * Rat(10, 3), 10) |
| 285 | self.assertEqual(Rat(10, 5) * 0.5, 1.0) |
| 286 | self.assertEqual(0.5 * Rat(10, 5), 1.0) |
| 287 | |
| 288 | def test_div(self): |
| 289 | self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3)) |
| 290 | self.assertEqual(Rat(10, 3) / 3, Rat(10, 9)) |
| 291 | self.assertEqual(2 / Rat(5), Rat(2, 5)) |
| 292 | self.assertEqual(3.0 * Rat(1, 2), 1.5) |
| 293 | self.assertEqual(Rat(1, 2) * 3.0, 1.5) |
| 294 | |
| 295 | def test_floordiv(self): |
| 296 | self.assertEqual(Rat(10) // Rat(4), 2) |
| 297 | self.assertEqual(Rat(10, 3) // Rat(4, 3), 2) |
| 298 | self.assertEqual(Rat(10) // 4, 2) |
| 299 | self.assertEqual(10 // Rat(4), 2) |
| 300 | |
| 301 | def test_eq(self): |
| 302 | self.assertEqual(Rat(10), Rat(20, 2)) |
| 303 | self.assertEqual(Rat(10), 10) |
| 304 | self.assertEqual(10, Rat(10)) |
| 305 | self.assertEqual(Rat(10), 10.0) |
| 306 | self.assertEqual(10.0, Rat(10)) |
| 307 | |
| 308 | def test_future_div(self): |
| 309 | exec future_test |
| 310 | |
| 311 | # XXX Ran out of steam; TO DO: divmod, div, future division |
| 312 | |
| 313 | future_test = """ |
| 314 | from __future__ import division |
| 315 | self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3)) |
| 316 | self.assertEqual(Rat(10, 3) / 3, Rat(10, 9)) |
| 317 | self.assertEqual(2 / Rat(5), Rat(2, 5)) |
| 318 | self.assertEqual(3.0 * Rat(1, 2), 1.5) |
| 319 | self.assertEqual(Rat(1, 2) * 3.0, 1.5) |
Tim Peters | 9fa96be | 2001-08-17 23:04:59 +0000 | [diff] [blame] | 320 | self.assertEqual(eval('1/2'), 0.5) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 321 | """ |
| 322 | |
| 323 | test_support.run_unittest(RatTestCase) |