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 | |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 3 | import unittest |
Benjamin Peterson | ee8712c | 2008-05-20 21:35:26 +0000 | [diff] [blame] | 4 | from test import support |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 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.""" |
Guido van Rossum | e2a383d | 2007-01-15 16:59:06 +0000 | [diff] [blame] | 14 | return isinstance(x, int) or isinstance(x, int) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 15 | |
| 16 | def isnum(x): |
| 17 | """Test whether an object is an instance of a built-in numeric type.""" |
Guido van Rossum | e2a383d | 2007-01-15 16:59:06 +0000 | [diff] [blame] | 18 | for T in int, int, float, complex: |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 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 | |
Guido van Rossum | e2a383d | 2007-01-15 16:59:06 +0000 | [diff] [blame] | 33 | def __init__(self, num=0, den=1): |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 34 | """Constructor: Rat([num[, den]]). |
| 35 | |
| 36 | The arguments must be ints or longs, and default to (0, 1).""" |
| 37 | if not isint(num): |
Collin Winter | 3add4d7 | 2007-08-29 23:37:32 +0000 | [diff] [blame] | 38 | raise TypeError("Rat numerator must be int or long (%r)" % num) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 39 | if not isint(den): |
Collin Winter | 3add4d7 | 2007-08-29 23:37:32 +0000 | [diff] [blame] | 40 | raise TypeError("Rat denominator must be int or long (%r)" % den) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 41 | # But the zero is always on |
| 42 | if den == 0: |
Collin Winter | 3add4d7 | 2007-08-29 23:37:32 +0000 | [diff] [blame] | 43 | raise ZeroDivisionError("zero denominator") |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 44 | g = gcd(den, num) |
Guido van Rossum | e2a383d | 2007-01-15 16:59:06 +0000 | [diff] [blame] | 45 | self.__num = int(num//g) |
| 46 | self.__den = int(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 |
Guido van Rossum | 8bce4ac | 2001-09-06 21:56:42 +0000 | [diff] [blame] | 51 | num = property(_get_num, None) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 52 | |
| 53 | def _get_den(self): |
| 54 | """Accessor function for read-only 'den' attribute of Rat.""" |
| 55 | return self.__den |
Guido van Rossum | 8bce4ac | 2001-09-06 21:56:42 +0000 | [diff] [blame] | 56 | den = property(_get_den, None) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 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: |
Collin Winter | 3add4d7 | 2007-08-29 23:37:32 +0000 | [diff] [blame] | 76 | raise OverflowError("%s too large to convert to int" % |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 77 | repr(self)) |
Collin Winter | 3add4d7 | 2007-08-29 23:37:32 +0000 | [diff] [blame] | 78 | raise ValueError("can't convert %s to int" % repr(self)) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 79 | |
| 80 | def __long__(self): |
| 81 | """Convert a Rat to an long; self.den must be 1.""" |
| 82 | if self.__den == 1: |
Guido van Rossum | e2a383d | 2007-01-15 16:59:06 +0000 | [diff] [blame] | 83 | return int(self.__num) |
Collin Winter | 3add4d7 | 2007-08-29 23:37:32 +0000 | [diff] [blame] | 84 | raise ValueError("can't convert %s to long" % repr(self)) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 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 | |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 143 | def __rtruediv__(self, other): |
| 144 | """Divide two Rats, or a Rat and a number (reversed args).""" |
| 145 | if isRat(other): |
| 146 | return Rat(other.__num*self.__den, other.__den*self.__num) |
| 147 | if isint(other): |
| 148 | return Rat(other*self.__den, self.__num) |
| 149 | if isnum(other): |
| 150 | return other / float(self) |
| 151 | return NotImplemented |
| 152 | |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 153 | def __floordiv__(self, other): |
| 154 | """Divide two Rats, returning the floored result.""" |
| 155 | if isint(other): |
| 156 | other = Rat(other) |
| 157 | elif not isRat(other): |
| 158 | return NotImplemented |
| 159 | x = self/other |
| 160 | return x.__num // x.__den |
| 161 | |
| 162 | def __rfloordiv__(self, other): |
| 163 | """Divide two Rats, returning the floored result (reversed args).""" |
| 164 | x = other/self |
| 165 | return x.__num // x.__den |
| 166 | |
| 167 | def __divmod__(self, other): |
| 168 | """Divide two Rats, returning quotient and remainder.""" |
| 169 | if isint(other): |
| 170 | other = Rat(other) |
| 171 | elif not isRat(other): |
| 172 | return NotImplemented |
| 173 | x = self//other |
| 174 | return (x, self - other * x) |
| 175 | |
| 176 | def __rdivmod__(self, other): |
Brett Cannon | 53e9a8b | 2005-04-30 05:50:19 +0000 | [diff] [blame] | 177 | """Divide two Rats, returning quotient and remainder (reversed args).""" |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 178 | if isint(other): |
| 179 | other = Rat(other) |
| 180 | elif not isRat(other): |
| 181 | return NotImplemented |
| 182 | return divmod(other, self) |
| 183 | |
| 184 | def __mod__(self, other): |
| 185 | """Take one Rat modulo another.""" |
| 186 | return divmod(self, other)[1] |
| 187 | |
| 188 | def __rmod__(self, other): |
| 189 | """Take one Rat modulo another (reversed args).""" |
| 190 | return divmod(other, self)[1] |
| 191 | |
| 192 | def __eq__(self, other): |
| 193 | """Compare two Rats for equality.""" |
| 194 | if isint(other): |
| 195 | return self.__den == 1 and self.__num == other |
| 196 | if isRat(other): |
| 197 | return self.__num == other.__num and self.__den == other.__den |
| 198 | if isnum(other): |
| 199 | return float(self) == other |
| 200 | return NotImplemented |
| 201 | |
| 202 | def __ne__(self, other): |
| 203 | """Compare two Rats for inequality.""" |
| 204 | return not self == other |
| 205 | |
| 206 | class RatTestCase(unittest.TestCase): |
| 207 | """Unit tests for Rat class and its support utilities.""" |
| 208 | |
| 209 | def test_gcd(self): |
| 210 | self.assertEqual(gcd(10, 12), 2) |
| 211 | self.assertEqual(gcd(10, 15), 5) |
| 212 | self.assertEqual(gcd(10, 11), 1) |
| 213 | self.assertEqual(gcd(100, 15), 5) |
| 214 | self.assertEqual(gcd(-10, 2), -2) |
| 215 | self.assertEqual(gcd(10, -2), 2) |
| 216 | self.assertEqual(gcd(-10, -2), -2) |
| 217 | for i in range(1, 20): |
| 218 | for j in range(1, 20): |
| 219 | self.assert_(gcd(i, j) > 0) |
| 220 | self.assert_(gcd(-i, j) < 0) |
| 221 | self.assert_(gcd(i, -j) > 0) |
| 222 | self.assert_(gcd(-i, -j) < 0) |
| 223 | |
| 224 | def test_constructor(self): |
| 225 | a = Rat(10, 15) |
| 226 | self.assertEqual(a.num, 2) |
| 227 | self.assertEqual(a.den, 3) |
Guido van Rossum | e2a383d | 2007-01-15 16:59:06 +0000 | [diff] [blame] | 228 | a = Rat(10, 15) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 229 | self.assertEqual(a.num, 2) |
| 230 | self.assertEqual(a.den, 3) |
| 231 | a = Rat(10, -15) |
| 232 | self.assertEqual(a.num, -2) |
| 233 | self.assertEqual(a.den, 3) |
| 234 | a = Rat(-10, 15) |
| 235 | self.assertEqual(a.num, -2) |
| 236 | self.assertEqual(a.den, 3) |
| 237 | a = Rat(-10, -15) |
| 238 | self.assertEqual(a.num, 2) |
| 239 | self.assertEqual(a.den, 3) |
| 240 | a = Rat(7) |
| 241 | self.assertEqual(a.num, 7) |
| 242 | self.assertEqual(a.den, 1) |
| 243 | try: |
| 244 | a = Rat(1, 0) |
| 245 | except ZeroDivisionError: |
| 246 | pass |
| 247 | else: |
| 248 | self.fail("Rat(1, 0) didn't raise ZeroDivisionError") |
| 249 | for bad in "0", 0.0, 0j, (), [], {}, None, Rat, unittest: |
| 250 | try: |
| 251 | a = Rat(bad) |
| 252 | except TypeError: |
| 253 | pass |
| 254 | else: |
| 255 | self.fail("Rat(%r) didn't raise TypeError" % bad) |
| 256 | try: |
| 257 | a = Rat(1, bad) |
| 258 | except TypeError: |
| 259 | pass |
| 260 | else: |
| 261 | self.fail("Rat(1, %r) didn't raise TypeError" % bad) |
| 262 | |
| 263 | def test_add(self): |
| 264 | self.assertEqual(Rat(2, 3) + Rat(1, 3), 1) |
| 265 | self.assertEqual(Rat(2, 3) + 1, Rat(5, 3)) |
| 266 | self.assertEqual(1 + Rat(2, 3), Rat(5, 3)) |
| 267 | self.assertEqual(1.0 + Rat(1, 2), 1.5) |
| 268 | self.assertEqual(Rat(1, 2) + 1.0, 1.5) |
| 269 | |
| 270 | def test_sub(self): |
| 271 | self.assertEqual(Rat(7, 2) - Rat(7, 5), Rat(21, 10)) |
| 272 | self.assertEqual(Rat(7, 5) - 1, Rat(2, 5)) |
| 273 | self.assertEqual(1 - Rat(3, 5), Rat(2, 5)) |
| 274 | self.assertEqual(Rat(3, 2) - 1.0, 0.5) |
| 275 | self.assertEqual(1.0 - Rat(1, 2), 0.5) |
| 276 | |
| 277 | def test_mul(self): |
| 278 | self.assertEqual(Rat(2, 3) * Rat(5, 7), Rat(10, 21)) |
| 279 | self.assertEqual(Rat(10, 3) * 3, 10) |
| 280 | self.assertEqual(3 * Rat(10, 3), 10) |
| 281 | self.assertEqual(Rat(10, 5) * 0.5, 1.0) |
| 282 | self.assertEqual(0.5 * Rat(10, 5), 1.0) |
| 283 | |
| 284 | def test_div(self): |
| 285 | self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3)) |
| 286 | self.assertEqual(Rat(10, 3) / 3, Rat(10, 9)) |
| 287 | self.assertEqual(2 / Rat(5), Rat(2, 5)) |
| 288 | self.assertEqual(3.0 * Rat(1, 2), 1.5) |
| 289 | self.assertEqual(Rat(1, 2) * 3.0, 1.5) |
| 290 | |
| 291 | def test_floordiv(self): |
| 292 | self.assertEqual(Rat(10) // Rat(4), 2) |
| 293 | self.assertEqual(Rat(10, 3) // Rat(4, 3), 2) |
| 294 | self.assertEqual(Rat(10) // 4, 2) |
| 295 | self.assertEqual(10 // Rat(4), 2) |
| 296 | |
| 297 | def test_eq(self): |
| 298 | self.assertEqual(Rat(10), Rat(20, 2)) |
| 299 | self.assertEqual(Rat(10), 10) |
| 300 | self.assertEqual(10, Rat(10)) |
| 301 | self.assertEqual(Rat(10), 10.0) |
| 302 | self.assertEqual(10.0, Rat(10)) |
| 303 | |
Benjamin Peterson | 42806ba | 2008-06-28 23:34:00 +0000 | [diff] [blame] | 304 | def test_true_div(self): |
| 305 | self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3)) |
| 306 | self.assertEqual(Rat(10, 3) / 3, Rat(10, 9)) |
| 307 | self.assertEqual(2 / Rat(5), Rat(2, 5)) |
| 308 | self.assertEqual(3.0 * Rat(1, 2), 1.5) |
| 309 | self.assertEqual(Rat(1, 2) * 3.0, 1.5) |
| 310 | self.assertEqual(eval('1/2'), 0.5) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 311 | |
| 312 | # XXX Ran out of steam; TO DO: divmod, div, future division |
| 313 | |
Fred Drake | 2e2be37 | 2001-09-20 21:33:42 +0000 | [diff] [blame] | 314 | def test_main(): |
Benjamin Peterson | ee8712c | 2008-05-20 21:35:26 +0000 | [diff] [blame] | 315 | support.run_unittest(RatTestCase) |
Fred Drake | 2e2be37 | 2001-09-20 21:33:42 +0000 | [diff] [blame] | 316 | |
| 317 | |
| 318 | if __name__ == "__main__": |
| 319 | test_main() |