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