@@ -135,7 +135,7 @@ def __float__(self):
135
135
assert False , "__float__ should not be invoked"
136
136
137
137
138
- class DummyFraction (fractions .Fraction ):
138
+ class DummyFraction (quicktions .Fraction ):
139
139
"""Dummy Fraction subclass for copy and deepcopy testing."""
140
140
141
141
@@ -200,6 +200,197 @@ def test_quicktions_limits(self):
200
200
def _components (r ):
201
201
return (r .numerator , r .denominator )
202
202
203
+ def typed_approx_eq (a , b ):
204
+ return type (a ) == type (b ) and (a == b or math .isclose (a , b ))
205
+
206
+ class Symbolic :
207
+ """Simple non-numeric class for testing mixed arithmetic.
208
+ It is not Integral, Rational, Real or Complex, and cannot be conveted
209
+ to int, float or complex. but it supports some arithmetic operations.
210
+ """
211
+ def __init__ (self , value ):
212
+ self .value = value
213
+ def __mul__ (self , other ):
214
+ if isinstance (other , F ):
215
+ return NotImplemented
216
+ return self .__class__ (f'{ self } * { other } ' )
217
+ def __rmul__ (self , other ):
218
+ return self .__class__ (f'{ other } * { self } ' )
219
+ def __truediv__ (self , other ):
220
+ if isinstance (other , F ):
221
+ return NotImplemented
222
+ return self .__class__ (f'{ self } / { other } ' )
223
+ def __rtruediv__ (self , other ):
224
+ return self .__class__ (f'{ other } / { self } ' )
225
+ def __mod__ (self , other ):
226
+ if isinstance (other , F ):
227
+ return NotImplemented
228
+ return self .__class__ (f'{ self } % { other } ' )
229
+ def __rmod__ (self , other ):
230
+ return self .__class__ (f'{ other } % { self } ' )
231
+ def __pow__ (self , other ):
232
+ if isinstance (other , F ):
233
+ return NotImplemented
234
+ return self .__class__ (f'{ self } ** { other } ' )
235
+ def __rpow__ (self , other ):
236
+ return self .__class__ (f'{ other } ** { self } ' )
237
+ def __eq__ (self , other ):
238
+ if other .__class__ != self .__class__ :
239
+ return NotImplemented
240
+ return self .value == other .value
241
+ def __str__ (self ):
242
+ return f'{ self .value } '
243
+ def __repr__ (self ):
244
+ return f'{ self .__class__ .__name__ } ({ self .value !r} )'
245
+
246
+ class SymbolicReal (Symbolic ):
247
+ pass
248
+ numbers .Real .register (SymbolicReal )
249
+
250
+ class SymbolicComplex (Symbolic ):
251
+ pass
252
+ numbers .Complex .register (SymbolicComplex )
253
+
254
+ class Rat :
255
+ """Simple Rational class for testing mixed arithmetic."""
256
+ def __init__ (self , n , d ):
257
+ self .numerator = n
258
+ self .denominator = d
259
+ def __mul__ (self , other ):
260
+ if isinstance (other , F ):
261
+ return NotImplemented
262
+ return self .__class__ (self .numerator * other .numerator ,
263
+ self .denominator * other .denominator )
264
+ def __rmul__ (self , other ):
265
+ return self .__class__ (other .numerator * self .numerator ,
266
+ other .denominator * self .denominator )
267
+ def __truediv__ (self , other ):
268
+ if isinstance (other , F ):
269
+ return NotImplemented
270
+ return self .__class__ (self .numerator * other .denominator ,
271
+ self .denominator * other .numerator )
272
+ def __rtruediv__ (self , other ):
273
+ return self .__class__ (other .numerator * self .denominator ,
274
+ other .denominator * self .numerator )
275
+ def __mod__ (self , other ):
276
+ if isinstance (other , F ):
277
+ return NotImplemented
278
+ d = self .denominator * other .numerator
279
+ return self .__class__ (self .numerator * other .denominator % d , d )
280
+ def __rmod__ (self , other ):
281
+ d = other .denominator * self .numerator
282
+ return self .__class__ (other .numerator * self .denominator % d , d )
283
+
284
+ return self .__class__ (other .numerator / self .numerator ,
285
+ other .denominator / self .denominator )
286
+ def __pow__ (self , other ):
287
+ if isinstance (other , F ):
288
+ return NotImplemented
289
+ return self .__class__ (self .numerator ** other ,
290
+ self .denominator ** other )
291
+ def __float__ (self ):
292
+ return self .numerator / self .denominator
293
+ def __eq__ (self , other ):
294
+ if self .__class__ != other .__class__ :
295
+ return NotImplemented
296
+ return (typed_approx_eq (self .numerator , other .numerator ) and
297
+ typed_approx_eq (self .denominator , other .denominator ))
298
+ def __repr__ (self ):
299
+ return f'{ self .__class__ .__name__ } ({ self .numerator !r} , { self .denominator !r} )'
300
+ numbers .Rational .register (Rat )
301
+
302
+ class Root :
303
+ """Simple Real class for testing mixed arithmetic."""
304
+ def __init__ (self , v , n = F (2 )):
305
+ self .base = v
306
+ self .degree = n
307
+ def __mul__ (self , other ):
308
+ if isinstance (other , F ):
309
+ return NotImplemented
310
+ return self .__class__ (self .base * other ** self .degree , self .degree )
311
+ def __rmul__ (self , other ):
312
+ return self .__class__ (other ** self .degree * self .base , self .degree )
313
+ def __truediv__ (self , other ):
314
+ if isinstance (other , F ):
315
+ return NotImplemented
316
+ return self .__class__ (self .base / other ** self .degree , self .degree )
317
+ def __rtruediv__ (self , other ):
318
+ return self .__class__ (other ** self .degree / self .base , self .degree )
319
+ def __pow__ (self , other ):
320
+ if isinstance (other , F ):
321
+ return NotImplemented
322
+ return self .__class__ (self .base , self .degree / other )
323
+ def __float__ (self ):
324
+ return float (self .base ) ** (1 / float (self .degree ))
325
+ def __eq__ (self , other ):
326
+ if self .__class__ != other .__class__ :
327
+ return NotImplemented
328
+ return typed_approx_eq (self .base , other .base ) and typed_approx_eq (self .degree , other .degree )
329
+ def __repr__ (self ):
330
+ return f'{ self .__class__ .__name__ } ({ self .base !r} , { self .degree !r} )'
331
+ numbers .Real .register (Root )
332
+
333
+ class Polar :
334
+ """Simple Complex class for testing mixed arithmetic."""
335
+ def __init__ (self , r , phi ):
336
+ self .r = r
337
+ self .phi = phi
338
+ def __mul__ (self , other ):
339
+ if isinstance (other , F ):
340
+ return NotImplemented
341
+ return self .__class__ (self .r * other , self .phi )
342
+ def __rmul__ (self , other ):
343
+ return self .__class__ (other * self .r , self .phi )
344
+ def __truediv__ (self , other ):
345
+ if isinstance (other , F ):
346
+ return NotImplemented
347
+ return self .__class__ (self .r / other , self .phi )
348
+ def __rtruediv__ (self , other ):
349
+ return self .__class__ (other / self .r , - self .phi )
350
+ def __pow__ (self , other ):
351
+ if isinstance (other , F ):
352
+ return NotImplemented
353
+ return self .__class__ (self .r ** other , self .phi * other )
354
+ def __eq__ (self , other ):
355
+ if self .__class__ != other .__class__ :
356
+ return NotImplemented
357
+ return typed_approx_eq (self .r , other .r ) and typed_approx_eq (self .phi , other .phi )
358
+ def __repr__ (self ):
359
+ return f'{ self .__class__ .__name__ } ({ self .r !r} , { self .phi !r} )'
360
+ numbers .Complex .register (Polar )
361
+
362
+ class Rect :
363
+ """Other simple Complex class for testing mixed arithmetic."""
364
+ def __init__ (self , x , y ):
365
+ self .x = x
366
+ self .y = y
367
+ def __mul__ (self , other ):
368
+ if isinstance (other , F ):
369
+ return NotImplemented
370
+ return self .__class__ (self .x * other , self .y * other )
371
+ def __rmul__ (self , other ):
372
+ return self .__class__ (other * self .x , other * self .y )
373
+ def __truediv__ (self , other ):
374
+ if isinstance (other , F ):
375
+ return NotImplemented
376
+ return self .__class__ (self .x / other , self .y / other )
377
+ def __rtruediv__ (self , other ):
378
+ r = self .x * self .x + self .y * self .y
379
+ return self .__class__ (other * (self .x / r ), other * (self .y / r ))
380
+ def __rpow__ (self , other ):
381
+ return Polar (other ** self .x , math .log (other ) * self .y )
382
+ def __complex__ (self ):
383
+ return complex (self .x , self .y )
384
+ def __eq__ (self , other ):
385
+ if self .__class__ != other .__class__ :
386
+ return NotImplemented
387
+ return typed_approx_eq (self .x , other .x ) and typed_approx_eq (self .y , other .y )
388
+ def __repr__ (self ):
389
+ return f'{ self .__class__ .__name__ } ({ self .x !r} , { self .y !r} )'
390
+ numbers .Complex .register (Rect )
391
+
392
+ class RectComplex (Rect , complex ):
393
+ pass
203
394
204
395
class FractionTest (unittest .TestCase ):
205
396
@@ -795,20 +986,66 @@ def testMixedArithmetic(self):
795
986
self .assertTypedEquals (0.9 , 1.0 - F (1 , 10 ))
796
987
self .assertTypedEquals (0.9 + 0j , (1.0 + 0j ) - F (1 , 10 ))
797
988
989
+ def testMixedMultiplication (self ):
798
990
self .assertTypedEquals (F (1 , 10 ), F (1 , 10 ) * 1 )
799
991
self .assertTypedEquals (0.1 , F (1 , 10 ) * 1.0 )
800
992
self .assertTypedEquals (0.1 + 0j , F (1 , 10 ) * (1.0 + 0j ))
801
993
self .assertTypedEquals (F (1 , 10 ), 1 * F (1 , 10 ))
802
994
self .assertTypedEquals (0.1 , 1.0 * F (1 , 10 ))
803
995
self .assertTypedEquals (0.1 + 0j , (1.0 + 0j ) * F (1 , 10 ))
804
996
997
+ self .assertTypedEquals (F (3 , 2 ) * DummyFraction (5 , 3 ), F (5 , 2 ))
998
+ self .assertTypedEquals (DummyFraction (5 , 3 ) * F (3 , 2 ), F (5 , 2 ))
999
+ self .assertTypedEquals (F (3 , 2 ) * Rat (5 , 3 ), Rat (15 , 6 ))
1000
+ self .assertTypedEquals (Rat (5 , 3 ) * F (3 , 2 ), F (5 , 2 ))
1001
+
1002
+ self .assertTypedEquals (F (3 , 2 ) * Root (4 ), Root (F (9 , 1 )))
1003
+ self .assertTypedEquals (Root (4 ) * F (3 , 2 ), 3.0 )
1004
+ self .assertEqual (F (3 , 2 ) * SymbolicReal ('X' ), SymbolicReal ('3/2 * X' ))
1005
+ self .assertRaises (TypeError , operator .mul , SymbolicReal ('X' ), F (3 , 2 ))
1006
+
1007
+ self .assertTypedEquals (F (3 , 2 ) * Polar (4 , 2 ), Polar (F (6 , 1 ), 2 ))
1008
+ self .assertTypedEquals (F (3 , 2 ) * Polar (4.0 , 2 ), Polar (6.0 , 2 ))
1009
+ self .assertTypedEquals (F (3 , 2 ) * Rect (4 , 3 ), Rect (F (6 , 1 ), F (9 , 2 )))
1010
+ self .assertTypedEquals (F (3 , 2 ) * RectComplex (4 , 3 ), RectComplex (6.0 + 0j , 4.5 + 0j ))
1011
+ self .assertRaises (TypeError , operator .mul , Polar (4 , 2 ), F (3 , 2 ))
1012
+ self .assertTypedEquals (Rect (4 , 3 ) * F (3 , 2 ), 6.0 + 4.5j )
1013
+ self .assertEqual (F (3 , 2 ) * SymbolicComplex ('X' ), SymbolicComplex ('3/2 * X' ))
1014
+ self .assertRaises (TypeError , operator .mul , SymbolicComplex ('X' ), F (3 , 2 ))
1015
+
1016
+ self .assertEqual (F (3 , 2 ) * Symbolic ('X' ), Symbolic ('3/2 * X' ))
1017
+ self .assertRaises (TypeError , operator .mul , Symbolic ('X' ), F (3 , 2 ))
1018
+
1019
+ def testMixedDivision (self ):
805
1020
self .assertTypedEquals (F (1 , 10 ), F (1 , 10 ) / 1 )
806
1021
self .assertTypedEquals (0.1 , F (1 , 10 ) / 1.0 )
807
1022
self .assertTypedEquals (0.1 + 0j , F (1 , 10 ) / (1.0 + 0j ))
808
1023
self .assertTypedEquals (F (10 , 1 ), 1 / F (1 , 10 ))
809
1024
self .assertTypedEquals (10.0 , 1.0 / F (1 , 10 ))
810
1025
self .assertTypedEquals (10.0 + 0j , (1.0 + 0j ) / F (1 , 10 ))
811
1026
1027
+ self .assertTypedEquals (F (3 , 2 ) / DummyFraction (3 , 5 ), F (5 , 2 ))
1028
+ self .assertTypedEquals (DummyFraction (5 , 3 ) / F (2 , 3 ), F (5 , 2 ))
1029
+ self .assertTypedEquals (F (3 , 2 ) / Rat (3 , 5 ), Rat (15 , 6 ))
1030
+ self .assertTypedEquals (Rat (5 , 3 ) / F (2 , 3 ), F (5 , 2 ))
1031
+
1032
+ self .assertTypedEquals (F (2 , 3 ) / Root (4 ), Root (F (1 , 9 )))
1033
+ self .assertTypedEquals (Root (4 ) / F (2 , 3 ), 3.0 )
1034
+ self .assertEqual (F (3 , 2 ) / SymbolicReal ('X' ), SymbolicReal ('3/2 / X' ))
1035
+ self .assertRaises (TypeError , operator .truediv , SymbolicReal ('X' ), F (3 , 2 ))
1036
+
1037
+ self .assertTypedEquals (F (3 , 2 ) / Polar (4 , 2 ), Polar (F (3 , 8 ), - 2 ))
1038
+ self .assertTypedEquals (F (3 , 2 ) / Polar (4.0 , 2 ), Polar (0.375 , - 2 ))
1039
+ self .assertTypedEquals (F (3 , 2 ) / Rect (4 , 3 ), Rect (0.24 , 0.18 ))
1040
+ self .assertRaises (TypeError , operator .truediv , Polar (4 , 2 ), F (2 , 3 ))
1041
+ self .assertTypedEquals (Rect (4 , 3 ) / F (2 , 3 ), 6.0 + 4.5j )
1042
+ self .assertEqual (F (3 , 2 ) / SymbolicComplex ('X' ), SymbolicComplex ('3/2 / X' ))
1043
+ self .assertRaises (TypeError , operator .truediv , SymbolicComplex ('X' ), F (3 , 2 ))
1044
+
1045
+ self .assertEqual (F (3 , 2 ) / Symbolic ('X' ), Symbolic ('3/2 / X' ))
1046
+ self .assertRaises (TypeError , operator .truediv , Symbolic ('X' ), F (2 , 3 ))
1047
+
1048
+ def testMixedIntegerDivision (self ):
812
1049
self .assertTypedEquals (0 , F (1 , 10 ) // 1 )
813
1050
self .assertTypedEquals (0.0 , F (1 , 10 ) // 1.0 )
814
1051
self .assertTypedEquals (10 , 1 // F (1 , 10 ))
@@ -835,6 +1072,26 @@ def testMixedArithmetic(self):
835
1072
self .assertTypedTupleEquals (divmod (- 0.1 , float ('inf' )), divmod (F (- 1 , 10 ), float ('inf' )))
836
1073
self .assertTypedTupleEquals (divmod (- 0.1 , float ('-inf' )), divmod (F (- 1 , 10 ), float ('-inf' )))
837
1074
1075
+ self .assertTypedEquals (F (3 , 2 ) % DummyFraction (3 , 5 ), F (3 , 10 ))
1076
+ self .assertTypedEquals (DummyFraction (5 , 3 ) % F (2 , 3 ), F (1 , 3 ))
1077
+ self .assertTypedEquals (F (3 , 2 ) % Rat (3 , 5 ), Rat (3 , 6 ))
1078
+ self .assertTypedEquals (Rat (5 , 3 ) % F (2 , 3 ), F (1 , 3 ))
1079
+
1080
+ self .assertRaises (TypeError , operator .mod , F (2 , 3 ), Root (4 ))
1081
+ self .assertTypedEquals (Root (4 ) % F (3 , 2 ), 0.5 )
1082
+ self .assertEqual (F (3 , 2 ) % SymbolicReal ('X' ), SymbolicReal ('3/2 % X' ))
1083
+ self .assertRaises (TypeError , operator .mod , SymbolicReal ('X' ), F (3 , 2 ))
1084
+
1085
+ self .assertRaises (TypeError , operator .mod , F (3 , 2 ), Polar (4 , 2 ))
1086
+ self .assertRaises (TypeError , operator .mod , F (3 , 2 ), RectComplex (4 , 3 ))
1087
+ self .assertRaises (TypeError , operator .mod , Rect (4 , 3 ), F (2 , 3 ))
1088
+ self .assertEqual (F (3 , 2 ) % SymbolicComplex ('X' ), SymbolicComplex ('3/2 % X' ))
1089
+ self .assertRaises (TypeError , operator .mod , SymbolicComplex ('X' ), F (3 , 2 ))
1090
+
1091
+ self .assertEqual (F (3 , 2 ) % Symbolic ('X' ), Symbolic ('3/2 % X' ))
1092
+ self .assertRaises (TypeError , operator .mod , Symbolic ('X' ), F (2 , 3 ))
1093
+
1094
+ def testMixedPower (self ):
838
1095
# ** has more interesting conversion rules.
839
1096
self .assertTypedEquals (F (100 , 1 ), F (1 , 10 ) ** - 2 )
840
1097
self .assertTypedEquals (F (100 , 1 ), F (10 , 1 ) ** 2 )
@@ -855,6 +1112,40 @@ def testMixedArithmetic(self):
855
1112
self .assertRaises (ZeroDivisionError , operator .pow ,
856
1113
F (0 , 1 ), - 2 )
857
1114
1115
+ self .assertTypedEquals (F (3 , 2 ) ** Rat (3 , 1 ), F (27 , 8 ))
1116
+ self .assertTypedEquals (F (3 , 2 ) ** Rat (- 3 , 1 ), F (8 , 27 ))
1117
+ self .assertTypedEquals (F (- 3 , 2 ) ** Rat (- 3 , 1 ), F (- 8 , 27 ))
1118
+ self .assertTypedEquals (F (9 , 4 ) ** Rat (3 , 2 ), 3.375 )
1119
+ self .assertIsInstance (F (4 , 9 ) ** Rat (- 3 , 2 ), float )
1120
+ self .assertAlmostEqual (F (4 , 9 ) ** Rat (- 3 , 2 ), 3.375 )
1121
+ self .assertAlmostEqual (F (- 4 , 9 ) ** Rat (- 3 , 2 ), 3.375j )
1122
+ self .assertTypedEquals (Rat (9 , 4 ) ** F (3 , 2 ), 3.375 )
1123
+ self .assertTypedEquals (Rat (3 , 2 ) ** F (3 , 1 ), Rat (27 , 8 ))
1124
+ self .assertTypedEquals (Rat (3 , 2 ) ** F (- 3 , 1 ), F (8 , 27 ))
1125
+ self .assertIsInstance (Rat (4 , 9 ) ** F (- 3 , 2 ), float )
1126
+ self .assertAlmostEqual (Rat (4 , 9 ) ** F (- 3 , 2 ), 3.375 )
1127
+
1128
+ self .assertTypedEquals (Root (4 ) ** F (2 , 3 ), Root (4 , 3.0 ))
1129
+ self .assertTypedEquals (Root (4 ) ** F (2 , 1 ), Root (4 , F (1 )))
1130
+ self .assertTypedEquals (Root (4 ) ** F (- 2 , 1 ), Root (4 , - F (1 )))
1131
+ self .assertTypedEquals (Root (4 ) ** F (- 2 , 3 ), Root (4 , - 3.0 ))
1132
+ self .assertEqual (F (3 , 2 ) ** SymbolicReal ('X' ), SymbolicReal ('1.5 ** X' ))
1133
+ self .assertEqual (SymbolicReal ('X' ) ** F (3 , 2 ), SymbolicReal ('X ** 1.5' ))
1134
+
1135
+ self .assertTypedEquals (F (3 , 2 ) ** Rect (2 , 0 ), Polar (2.25 , 0.0 ))
1136
+ self .assertTypedEquals (F (1 , 1 ) ** Rect (2 , 3 ), Polar (1.0 , 0.0 ))
1137
+ self .assertTypedEquals (F (3 , 2 ) ** RectComplex (2 , 0 ), Polar (2.25 , 0.0 ))
1138
+ self .assertTypedEquals (F (1 , 1 ) ** RectComplex (2 , 3 ), Polar (1.0 , 0.0 ))
1139
+ self .assertTypedEquals (Polar (4 , 2 ) ** F (3 , 2 ), Polar (8.0 , 3.0 ))
1140
+ self .assertTypedEquals (Polar (4 , 2 ) ** F (3 , 1 ), Polar (64 , 6 ))
1141
+ self .assertTypedEquals (Polar (4 , 2 ) ** F (- 3 , 1 ), Polar (0.015625 , - 6 ))
1142
+ self .assertTypedEquals (Polar (4 , 2 ) ** F (- 3 , 2 ), Polar (0.125 , - 3.0 ))
1143
+ self .assertEqual (F (3 , 2 ) ** SymbolicComplex ('X' ), SymbolicComplex ('1.5 ** X' ))
1144
+ self .assertEqual (SymbolicComplex ('X' ) ** F (3 , 2 ), SymbolicComplex ('X ** 1.5' ))
1145
+
1146
+ self .assertEqual (F (3 , 2 ) ** Symbolic ('X' ), Symbolic ('1.5 ** X' ))
1147
+ self .assertEqual (Symbolic ('X' ) ** F (3 , 2 ), Symbolic ('X ** 1.5' ))
1148
+
858
1149
def testMixingWithDecimal (self ):
859
1150
# Decimal refuses mixed arithmetic (but not mixed comparisons)
860
1151
self .assertRaises (TypeError , operator .add ,
@@ -1087,7 +1378,7 @@ def numerator(self):
1087
1378
def denominator (self ):
1088
1379
return type (self )(1 )
1089
1380
1090
- f = fractions . Fraction (myint (1 * 3 ), myint (2 * 3 ))
1381
+ f = F (myint (1 * 3 ), myint (2 * 3 ))
1091
1382
self .assertEqual (f .numerator , 1 )
1092
1383
self .assertEqual (f .denominator , 2 )
1093
1384
self .assertEqual (type (f .numerator ), myint )
0 commit comments