blob: 2ca84f47d7232ad960368a347452a0a6271f0dd2 [file] [log] [blame]
Guido van Rossum64deef22001-08-08 22:27:20 +00001"""Tests for binary operators on subtypes of built-in types."""
2
3import test_support
4import unittest
5
6class 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
24def gcd(a, b):
25 """Greatest common divisor using Euclid's algorithm."""
26 while a:
27 a, b = b%a, a
28 return b
29
30def isint(x):
31 """Test whether an object is an instance of int or long."""
32 return isinstance(x, int) or isinstance(x, long)
33
34def 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
41def isRat(x):
42 """Test wheter an object is an instance of the Rat class."""
43 return isinstance(x, Rat)
44
45class 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
228class 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
331future_test = """
332from __future__ import division
333self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3))
334self.assertEqual(Rat(10, 3) / 3, Rat(10, 9))
335self.assertEqual(2 / Rat(5), Rat(2, 5))
336self.assertEqual(3.0 * Rat(1, 2), 1.5)
337self.assertEqual(Rat(1, 2) * 3.0, 1.5)
Tim Peters9fa96be2001-08-17 23:04:59 +0000338self.assertEqual(eval('1/2'), 0.5)
Guido van Rossum64deef22001-08-08 22:27:20 +0000339"""
340
341test_support.run_unittest(RatTestCase)