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