move sections
[python/dscho.git] / Lib / test / test_binop.py
blobb1ef626a9ac4b2954582fca94a0b5dcd808591cd
1 """Tests for binary operators on subtypes of built-in types."""
3 import unittest
4 from test import test_support
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
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)
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
23 def isRat(x):
24 """Test wheter an object is an instance of the Rat class."""
25 return isinstance(x, Rat)
27 class Rat(object):
29 """Rational number implemented as a normalized pair of longs."""
31 __slots__ = ['_Rat__num', '_Rat__den']
33 def __init__(self, num=0L, den=1L):
34 """Constructor: Rat([num[, den]]).
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)
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)
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)
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)
62 def __str__(self):
63 """Convert a Rat to a string resembling a decimal numeric value."""
64 return str(float(self))
66 def __float__(self):
67 """Convert a Rat to a float."""
68 return self.__num*1.0/self.__den
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)
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)
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
97 __radd__ = __add__
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
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
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
131 __rmul__ = __mul__
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
143 __div__ = __truediv__
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
155 __rdiv__ = __rtruediv__
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
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
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)
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)
188 def __mod__(self, other):
189 """Take one Rat modulo another."""
190 return divmod(self, other)[1]
192 def __rmod__(self, other):
193 """Take one Rat modulo another (reversed args)."""
194 return divmod(other, self)[1]
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
206 def __ne__(self, other):
207 """Compare two Rats for inequality."""
208 return not self == other
210 # Silence Py3k warning
211 __hash__ = None
213 class RatTestCase(unittest.TestCase):
214 """Unit tests for Rat class and its support utilities."""
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)
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)
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)
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)
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)
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)
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)
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))
311 def test_future_div(self):
312 exec future_test
314 # XXX Ran out of steam; TO DO: divmod, div, future division
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)
326 def test_main():
327 test_support.run_unittest(RatTestCase)
330 if __name__ == "__main__":
331 test_main()