blob: e9dbddcd9305f1cc3999958e026f66da27774547 [file] [log] [blame]
Guido van Rossum64deef22001-08-08 22:27:20 +00001"""Tests for binary operators on subtypes of built-in types."""
2
Guido van Rossum64deef22001-08-08 22:27:20 +00003import unittest
Benjamin Petersonee8712c2008-05-20 21:35:26 +00004from test import support
Mark Dickinson6f1d0492009-11-15 13:58:49 +00005from operator import eq, ne, lt, gt, le, ge
Robert Collinsd84b29f2015-08-07 10:22:54 +12006from abc import ABCMeta
Guido van Rossum64deef22001-08-08 22:27:20 +00007
Guido van Rossum64deef22001-08-08 22:27:20 +00008def gcd(a, b):
9 """Greatest common divisor using Euclid's algorithm."""
10 while a:
11 a, b = b%a, a
12 return b
13
14def isint(x):
Mark Dickinson5c2db372009-12-05 20:28:34 +000015 """Test whether an object is an instance of int."""
16 return isinstance(x, int)
Guido van Rossum64deef22001-08-08 22:27:20 +000017
18def isnum(x):
19 """Test whether an object is an instance of a built-in numeric type."""
Florent Xicluna02ea12b22010-07-28 16:39:41 +000020 for T in int, float, complex:
Guido van Rossum64deef22001-08-08 22:27:20 +000021 if isinstance(x, T):
22 return 1
23 return 0
24
25def isRat(x):
26 """Test wheter an object is an instance of the Rat class."""
27 return isinstance(x, Rat)
28
29class Rat(object):
30
Mark Dickinson5c2db372009-12-05 20:28:34 +000031 """Rational number implemented as a normalized pair of ints."""
Guido van Rossum64deef22001-08-08 22:27:20 +000032
33 __slots__ = ['_Rat__num', '_Rat__den']
34
Guido van Rossume2a383d2007-01-15 16:59:06 +000035 def __init__(self, num=0, den=1):
Guido van Rossum64deef22001-08-08 22:27:20 +000036 """Constructor: Rat([num[, den]]).
37
Mark Dickinson5c2db372009-12-05 20:28:34 +000038 The arguments must be ints, and default to (0, 1)."""
Guido van Rossum64deef22001-08-08 22:27:20 +000039 if not isint(num):
Mark Dickinson5c2db372009-12-05 20:28:34 +000040 raise TypeError("Rat numerator must be int (%r)" % num)
Guido van Rossum64deef22001-08-08 22:27:20 +000041 if not isint(den):
Mark Dickinson5c2db372009-12-05 20:28:34 +000042 raise TypeError("Rat denominator must be int (%r)" % den)
Guido van Rossum64deef22001-08-08 22:27:20 +000043 # But the zero is always on
44 if den == 0:
Collin Winter3add4d72007-08-29 23:37:32 +000045 raise ZeroDivisionError("zero denominator")
Guido van Rossum64deef22001-08-08 22:27:20 +000046 g = gcd(den, num)
Guido van Rossume2a383d2007-01-15 16:59:06 +000047 self.__num = int(num//g)
48 self.__den = int(den//g)
Guido van Rossum64deef22001-08-08 22:27:20 +000049
50 def _get_num(self):
51 """Accessor function for read-only 'num' attribute of Rat."""
52 return self.__num
Guido van Rossum8bce4ac2001-09-06 21:56:42 +000053 num = property(_get_num, None)
Guido van Rossum64deef22001-08-08 22:27:20 +000054
55 def _get_den(self):
56 """Accessor function for read-only 'den' attribute of Rat."""
57 return self.__den
Guido van Rossum8bce4ac2001-09-06 21:56:42 +000058 den = property(_get_den, None)
Guido van Rossum64deef22001-08-08 22:27:20 +000059
60 def __repr__(self):
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +030061 """Convert a Rat to a string resembling a Rat constructor call."""
Guido van Rossum64deef22001-08-08 22:27:20 +000062 return "Rat(%d, %d)" % (self.__num, self.__den)
63
64 def __str__(self):
65 """Convert a Rat to a string resembling a decimal numeric value."""
66 return str(float(self))
67
68 def __float__(self):
69 """Convert a Rat to a float."""
70 return self.__num*1.0/self.__den
71
72 def __int__(self):
73 """Convert a Rat to an int; self.den must be 1."""
74 if self.__den == 1:
75 try:
76 return int(self.__num)
77 except OverflowError:
Collin Winter3add4d72007-08-29 23:37:32 +000078 raise OverflowError("%s too large to convert to int" %
Guido van Rossum64deef22001-08-08 22:27:20 +000079 repr(self))
Collin Winter3add4d72007-08-29 23:37:32 +000080 raise ValueError("can't convert %s to int" % repr(self))
Guido van Rossum64deef22001-08-08 22:27:20 +000081
Guido van Rossum64deef22001-08-08 22:27:20 +000082 def __add__(self, other):
83 """Add two Rats, or a Rat and a number."""
84 if isint(other):
85 other = Rat(other)
86 if isRat(other):
87 return Rat(self.__num*other.__den + other.__num*self.__den,
88 self.__den*other.__den)
89 if isnum(other):
90 return float(self) + other
91 return NotImplemented
92
93 __radd__ = __add__
94
95 def __sub__(self, other):
96 """Subtract two Rats, or a Rat and a number."""
97 if isint(other):
98 other = Rat(other)
99 if isRat(other):
100 return Rat(self.__num*other.__den - other.__num*self.__den,
101 self.__den*other.__den)
102 if isnum(other):
103 return float(self) - other
104 return NotImplemented
105
106 def __rsub__(self, other):
107 """Subtract two Rats, or a Rat and a number (reversed args)."""
108 if isint(other):
109 other = Rat(other)
110 if isRat(other):
111 return Rat(other.__num*self.__den - self.__num*other.__den,
112 self.__den*other.__den)
113 if isnum(other):
114 return other - float(self)
115 return NotImplemented
116
117 def __mul__(self, other):
118 """Multiply two Rats, or a Rat and a number."""
119 if isRat(other):
120 return Rat(self.__num*other.__num, self.__den*other.__den)
121 if isint(other):
122 return Rat(self.__num*other, self.__den)
123 if isnum(other):
124 return float(self)*other
125 return NotImplemented
126
127 __rmul__ = __mul__
128
129 def __truediv__(self, other):
130 """Divide two Rats, or a Rat and a number."""
131 if isRat(other):
132 return Rat(self.__num*other.__den, self.__den*other.__num)
133 if isint(other):
134 return Rat(self.__num, self.__den*other)
135 if isnum(other):
136 return float(self) / other
137 return NotImplemented
138
Guido van Rossum64deef22001-08-08 22:27:20 +0000139 def __rtruediv__(self, other):
140 """Divide two Rats, or a Rat and a number (reversed args)."""
141 if isRat(other):
142 return Rat(other.__num*self.__den, other.__den*self.__num)
143 if isint(other):
144 return Rat(other*self.__den, self.__num)
145 if isnum(other):
146 return other / float(self)
147 return NotImplemented
148
Guido van Rossum64deef22001-08-08 22:27:20 +0000149 def __floordiv__(self, other):
150 """Divide two Rats, returning the floored result."""
151 if isint(other):
152 other = Rat(other)
153 elif not isRat(other):
154 return NotImplemented
155 x = self/other
156 return x.__num // x.__den
157
158 def __rfloordiv__(self, other):
159 """Divide two Rats, returning the floored result (reversed args)."""
160 x = other/self
161 return x.__num // x.__den
162
163 def __divmod__(self, other):
164 """Divide two Rats, returning quotient and remainder."""
165 if isint(other):
166 other = Rat(other)
167 elif not isRat(other):
168 return NotImplemented
169 x = self//other
170 return (x, self - other * x)
171
172 def __rdivmod__(self, other):
Brett Cannon53e9a8b2005-04-30 05:50:19 +0000173 """Divide two Rats, returning quotient and remainder (reversed args)."""
Guido van Rossum64deef22001-08-08 22:27:20 +0000174 if isint(other):
175 other = Rat(other)
176 elif not isRat(other):
177 return NotImplemented
178 return divmod(other, self)
179
180 def __mod__(self, other):
181 """Take one Rat modulo another."""
182 return divmod(self, other)[1]
183
184 def __rmod__(self, other):
185 """Take one Rat modulo another (reversed args)."""
186 return divmod(other, self)[1]
187
188 def __eq__(self, other):
189 """Compare two Rats for equality."""
190 if isint(other):
191 return self.__den == 1 and self.__num == other
192 if isRat(other):
193 return self.__num == other.__num and self.__den == other.__den
194 if isnum(other):
195 return float(self) == other
196 return NotImplemented
197
Guido van Rossum64deef22001-08-08 22:27:20 +0000198class RatTestCase(unittest.TestCase):
199 """Unit tests for Rat class and its support utilities."""
200
201 def test_gcd(self):
202 self.assertEqual(gcd(10, 12), 2)
203 self.assertEqual(gcd(10, 15), 5)
204 self.assertEqual(gcd(10, 11), 1)
205 self.assertEqual(gcd(100, 15), 5)
206 self.assertEqual(gcd(-10, 2), -2)
207 self.assertEqual(gcd(10, -2), 2)
208 self.assertEqual(gcd(-10, -2), -2)
209 for i in range(1, 20):
210 for j in range(1, 20):
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000211 self.assertTrue(gcd(i, j) > 0)
212 self.assertTrue(gcd(-i, j) < 0)
213 self.assertTrue(gcd(i, -j) > 0)
214 self.assertTrue(gcd(-i, -j) < 0)
Guido van Rossum64deef22001-08-08 22:27:20 +0000215
216 def test_constructor(self):
217 a = Rat(10, 15)
218 self.assertEqual(a.num, 2)
219 self.assertEqual(a.den, 3)
Guido van Rossum64deef22001-08-08 22:27:20 +0000220 a = Rat(10, -15)
221 self.assertEqual(a.num, -2)
222 self.assertEqual(a.den, 3)
223 a = Rat(-10, 15)
224 self.assertEqual(a.num, -2)
225 self.assertEqual(a.den, 3)
226 a = Rat(-10, -15)
227 self.assertEqual(a.num, 2)
228 self.assertEqual(a.den, 3)
229 a = Rat(7)
230 self.assertEqual(a.num, 7)
231 self.assertEqual(a.den, 1)
232 try:
233 a = Rat(1, 0)
234 except ZeroDivisionError:
235 pass
236 else:
237 self.fail("Rat(1, 0) didn't raise ZeroDivisionError")
238 for bad in "0", 0.0, 0j, (), [], {}, None, Rat, unittest:
239 try:
240 a = Rat(bad)
241 except TypeError:
242 pass
243 else:
244 self.fail("Rat(%r) didn't raise TypeError" % bad)
245 try:
246 a = Rat(1, bad)
247 except TypeError:
248 pass
249 else:
250 self.fail("Rat(1, %r) didn't raise TypeError" % bad)
251
252 def test_add(self):
253 self.assertEqual(Rat(2, 3) + Rat(1, 3), 1)
254 self.assertEqual(Rat(2, 3) + 1, Rat(5, 3))
255 self.assertEqual(1 + Rat(2, 3), Rat(5, 3))
256 self.assertEqual(1.0 + Rat(1, 2), 1.5)
257 self.assertEqual(Rat(1, 2) + 1.0, 1.5)
258
259 def test_sub(self):
260 self.assertEqual(Rat(7, 2) - Rat(7, 5), Rat(21, 10))
261 self.assertEqual(Rat(7, 5) - 1, Rat(2, 5))
262 self.assertEqual(1 - Rat(3, 5), Rat(2, 5))
263 self.assertEqual(Rat(3, 2) - 1.0, 0.5)
264 self.assertEqual(1.0 - Rat(1, 2), 0.5)
265
266 def test_mul(self):
267 self.assertEqual(Rat(2, 3) * Rat(5, 7), Rat(10, 21))
268 self.assertEqual(Rat(10, 3) * 3, 10)
269 self.assertEqual(3 * Rat(10, 3), 10)
270 self.assertEqual(Rat(10, 5) * 0.5, 1.0)
271 self.assertEqual(0.5 * Rat(10, 5), 1.0)
272
273 def test_div(self):
274 self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3))
275 self.assertEqual(Rat(10, 3) / 3, Rat(10, 9))
276 self.assertEqual(2 / Rat(5), Rat(2, 5))
277 self.assertEqual(3.0 * Rat(1, 2), 1.5)
278 self.assertEqual(Rat(1, 2) * 3.0, 1.5)
279
280 def test_floordiv(self):
281 self.assertEqual(Rat(10) // Rat(4), 2)
282 self.assertEqual(Rat(10, 3) // Rat(4, 3), 2)
283 self.assertEqual(Rat(10) // 4, 2)
284 self.assertEqual(10 // Rat(4), 2)
285
286 def test_eq(self):
287 self.assertEqual(Rat(10), Rat(20, 2))
288 self.assertEqual(Rat(10), 10)
289 self.assertEqual(10, Rat(10))
290 self.assertEqual(Rat(10), 10.0)
291 self.assertEqual(10.0, Rat(10))
292
Benjamin Peterson42806ba2008-06-28 23:34:00 +0000293 def test_true_div(self):
294 self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3))
295 self.assertEqual(Rat(10, 3) / 3, Rat(10, 9))
296 self.assertEqual(2 / Rat(5), Rat(2, 5))
297 self.assertEqual(3.0 * Rat(1, 2), 1.5)
298 self.assertEqual(Rat(1, 2) * 3.0, 1.5)
299 self.assertEqual(eval('1/2'), 0.5)
Guido van Rossum64deef22001-08-08 22:27:20 +0000300
301 # XXX Ran out of steam; TO DO: divmod, div, future division
302
Fred Drake2e2be372001-09-20 21:33:42 +0000303
Mark Dickinson6f1d0492009-11-15 13:58:49 +0000304class OperationLogger:
305 """Base class for classes with operation logging."""
306 def __init__(self, logger):
307 self.logger = logger
308 def log_operation(self, *args):
309 self.logger(*args)
310
311def op_sequence(op, *classes):
312 """Return the sequence of operations that results from applying
313 the operation `op` to instances of the given classes."""
314 log = []
315 instances = []
316 for c in classes:
317 instances.append(c(log.append))
318
319 try:
320 op(*instances)
321 except TypeError:
322 pass
323 return log
324
325class A(OperationLogger):
326 def __eq__(self, other):
327 self.log_operation('A.__eq__')
328 return NotImplemented
329 def __le__(self, other):
330 self.log_operation('A.__le__')
331 return NotImplemented
332 def __ge__(self, other):
333 self.log_operation('A.__ge__')
334 return NotImplemented
335
Robert Collinsd84b29f2015-08-07 10:22:54 +1200336class B(OperationLogger, metaclass=ABCMeta):
Mark Dickinson6f1d0492009-11-15 13:58:49 +0000337 def __eq__(self, other):
338 self.log_operation('B.__eq__')
339 return NotImplemented
340 def __le__(self, other):
341 self.log_operation('B.__le__')
342 return NotImplemented
343 def __ge__(self, other):
344 self.log_operation('B.__ge__')
345 return NotImplemented
346
347class C(B):
348 def __eq__(self, other):
349 self.log_operation('C.__eq__')
350 return NotImplemented
351 def __le__(self, other):
352 self.log_operation('C.__le__')
353 return NotImplemented
354 def __ge__(self, other):
355 self.log_operation('C.__ge__')
356 return NotImplemented
357
Robert Collinsd84b29f2015-08-07 10:22:54 +1200358class V(OperationLogger):
359 """Virtual subclass of B"""
360 def __eq__(self, other):
361 self.log_operation('V.__eq__')
362 return NotImplemented
363 def __le__(self, other):
364 self.log_operation('V.__le__')
365 return NotImplemented
366 def __ge__(self, other):
367 self.log_operation('V.__ge__')
368 return NotImplemented
369B.register(V)
370
371
Mark Dickinson6f1d0492009-11-15 13:58:49 +0000372class OperationOrderTests(unittest.TestCase):
373 def test_comparison_orders(self):
374 self.assertEqual(op_sequence(eq, A, A), ['A.__eq__', 'A.__eq__'])
375 self.assertEqual(op_sequence(eq, A, B), ['A.__eq__', 'B.__eq__'])
376 self.assertEqual(op_sequence(eq, B, A), ['B.__eq__', 'A.__eq__'])
377 # C is a subclass of B, so C.__eq__ is called first
378 self.assertEqual(op_sequence(eq, B, C), ['C.__eq__', 'B.__eq__'])
379 self.assertEqual(op_sequence(eq, C, B), ['C.__eq__', 'B.__eq__'])
380
381 self.assertEqual(op_sequence(le, A, A), ['A.__le__', 'A.__ge__'])
382 self.assertEqual(op_sequence(le, A, B), ['A.__le__', 'B.__ge__'])
383 self.assertEqual(op_sequence(le, B, A), ['B.__le__', 'A.__ge__'])
384 self.assertEqual(op_sequence(le, B, C), ['C.__ge__', 'B.__le__'])
385 self.assertEqual(op_sequence(le, C, B), ['C.__le__', 'B.__ge__'])
386
Robert Collinsd84b29f2015-08-07 10:22:54 +1200387 self.assertTrue(issubclass(V, B))
388 self.assertEqual(op_sequence(eq, B, V), ['B.__eq__', 'V.__eq__'])
389 self.assertEqual(op_sequence(le, B, V), ['B.__le__', 'V.__ge__'])
390
391
Fred Drake2e2be372001-09-20 21:33:42 +0000392if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500393 unittest.main()