Skip to content

Commit fe67af1

Browse files
gh-119189: Add more tests for mixed Fraction arithmetic (GH-119236)
1 parent 034cf0c commit fe67af1

File tree

1 file changed

+263
-0
lines changed

1 file changed

+263
-0
lines changed

Diff for: Lib/test/test_fractions.py

+263
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Tests for Lib/fractions.py."""
22

3+
import cmath
34
from decimal import Decimal
45
from test.support import requires_IEEE_754
56
import math
@@ -91,6 +92,187 @@ class DummyFraction(fractions.Fraction):
9192
def _components(r):
9293
return (r.numerator, r.denominator)
9394

95+
def typed_approx_eq(a, b):
96+
return type(a) == type(b) and (a == b or math.isclose(a, b))
97+
98+
class Symbolic:
99+
"""Simple non-numeric class for testing mixed arithmetic.
100+
It is not Integral, Rational, Real or Complex, and cannot be conveted
101+
to int, float or complex. but it supports some arithmetic operations.
102+
"""
103+
def __init__(self, value):
104+
self.value = value
105+
def __mul__(self, other):
106+
if isinstance(other, F):
107+
return NotImplemented
108+
return self.__class__(f'{self} * {other}')
109+
def __rmul__(self, other):
110+
return self.__class__(f'{other} * {self}')
111+
def __truediv__(self, other):
112+
if isinstance(other, F):
113+
return NotImplemented
114+
return self.__class__(f'{self} / {other}')
115+
def __rtruediv__(self, other):
116+
return self.__class__(f'{other} / {self}')
117+
def __mod__(self, other):
118+
if isinstance(other, F):
119+
return NotImplemented
120+
return self.__class__(f'{self} % {other}')
121+
def __rmod__(self, other):
122+
return self.__class__(f'{other} % {self}')
123+
def __pow__(self, other):
124+
if isinstance(other, F):
125+
return NotImplemented
126+
return self.__class__(f'{self} ** {other}')
127+
def __rpow__(self, other):
128+
return self.__class__(f'{other} ** {self}')
129+
def __eq__(self, other):
130+
if other.__class__ != self.__class__:
131+
return NotImplemented
132+
return self.value == other.value
133+
def __str__(self):
134+
return f'{self.value}'
135+
def __repr__(self):
136+
return f'{self.__class__.__name__}({self.value!r})'
137+
138+
class Rat:
139+
"""Simple Rational class for testing mixed arithmetic."""
140+
def __init__(self, n, d):
141+
self.numerator = n
142+
self.denominator = d
143+
def __mul__(self, other):
144+
if isinstance(other, F):
145+
return NotImplemented
146+
return self.__class__(self.numerator * other.numerator,
147+
self.denominator * other.denominator)
148+
def __rmul__(self, other):
149+
return self.__class__(other.numerator * self.numerator,
150+
other.denominator * self.denominator)
151+
def __truediv__(self, other):
152+
if isinstance(other, F):
153+
return NotImplemented
154+
return self.__class__(self.numerator * other.denominator,
155+
self.denominator * other.numerator)
156+
def __rtruediv__(self, other):
157+
return self.__class__(other.numerator * self.denominator,
158+
other.denominator * self.numerator)
159+
def __mod__(self, other):
160+
if isinstance(other, F):
161+
return NotImplemented
162+
d = self.denominator * other.numerator
163+
return self.__class__(self.numerator * other.denominator % d, d)
164+
def __rmod__(self, other):
165+
d = other.denominator * self.numerator
166+
return self.__class__(other.numerator * self.denominator % d, d)
167+
168+
return self.__class__(other.numerator / self.numerator,
169+
other.denominator / self.denominator)
170+
def __pow__(self, other):
171+
if isinstance(other, F):
172+
return NotImplemented
173+
return self.__class__(self.numerator ** other,
174+
self.denominator ** other)
175+
def __float__(self):
176+
return self.numerator / self.denominator
177+
def __eq__(self, other):
178+
if self.__class__ != other.__class__:
179+
return NotImplemented
180+
return (typed_approx_eq(self.numerator, other.numerator) and
181+
typed_approx_eq(self.denominator, other.denominator))
182+
def __repr__(self):
183+
return f'{self.__class__.__name__}({self.numerator!r}, {self.denominator!r})'
184+
numbers.Rational.register(Rat)
185+
186+
class Root:
187+
"""Simple Real class for testing mixed arithmetic."""
188+
def __init__(self, v, n=F(2)):
189+
self.base = v
190+
self.degree = n
191+
def __mul__(self, other):
192+
if isinstance(other, F):
193+
return NotImplemented
194+
return self.__class__(self.base * other**self.degree, self.degree)
195+
def __rmul__(self, other):
196+
return self.__class__(other**self.degree * self.base, self.degree)
197+
def __truediv__(self, other):
198+
if isinstance(other, F):
199+
return NotImplemented
200+
return self.__class__(self.base / other**self.degree, self.degree)
201+
def __rtruediv__(self, other):
202+
return self.__class__(other**self.degree / self.base, self.degree)
203+
def __pow__(self, other):
204+
if isinstance(other, F):
205+
return NotImplemented
206+
return self.__class__(self.base, self.degree / other)
207+
def __float__(self):
208+
return float(self.base) ** (1 / float(self.degree))
209+
def __eq__(self, other):
210+
if self.__class__ != other.__class__:
211+
return NotImplemented
212+
return typed_approx_eq(self.base, other.base) and typed_approx_eq(self.degree, other.degree)
213+
def __repr__(self):
214+
return f'{self.__class__.__name__}({self.base!r}, {self.degree!r})'
215+
numbers.Real.register(Root)
216+
217+
class Polar:
218+
"""Simple Complex class for testing mixed arithmetic."""
219+
def __init__(self, r, phi):
220+
self.r = r
221+
self.phi = phi
222+
def __mul__(self, other):
223+
if isinstance(other, F):
224+
return NotImplemented
225+
return self.__class__(self.r * other, self.phi)
226+
def __rmul__(self, other):
227+
return self.__class__(other * self.r, self.phi)
228+
def __truediv__(self, other):
229+
if isinstance(other, F):
230+
return NotImplemented
231+
return self.__class__(self.r / other, self.phi)
232+
def __rtruediv__(self, other):
233+
return self.__class__(other / self.r, -self.phi)
234+
def __pow__(self, other):
235+
if isinstance(other, F):
236+
return NotImplemented
237+
return self.__class__(self.r ** other, self.phi * other)
238+
def __eq__(self, other):
239+
if self.__class__ != other.__class__:
240+
return NotImplemented
241+
return typed_approx_eq(self.r, other.r) and typed_approx_eq(self.phi, other.phi)
242+
def __repr__(self):
243+
return f'{self.__class__.__name__}({self.r!r}, {self.phi!r})'
244+
numbers.Complex.register(Polar)
245+
246+
class Rect:
247+
"""Other simple Complex class for testing mixed arithmetic."""
248+
def __init__(self, x, y):
249+
self.x = x
250+
self.y = y
251+
def __mul__(self, other):
252+
if isinstance(other, F):
253+
return NotImplemented
254+
return self.__class__(self.x * other, self.y * other)
255+
def __rmul__(self, other):
256+
return self.__class__(other * self.x, other * self.y)
257+
def __truediv__(self, other):
258+
if isinstance(other, F):
259+
return NotImplemented
260+
return self.__class__(self.x / other, self.y / other)
261+
def __rtruediv__(self, other):
262+
r = self.x * self.x + self.y * self.y
263+
return self.__class__(other * (self.x / r), other * (self.y / r))
264+
def __rpow__(self, other):
265+
return Polar(other ** self.x, math.log(other) * self.y)
266+
def __complex__(self):
267+
return complex(self.x, self.y)
268+
def __eq__(self, other):
269+
if self.__class__ != other.__class__:
270+
return NotImplemented
271+
return typed_approx_eq(self.x, other.x) and typed_approx_eq(self.y, other.y)
272+
def __repr__(self):
273+
return f'{self.__class__.__name__}({self.x!r}, {self.y!r})'
274+
numbers.Complex.register(Rect)
275+
94276

95277
class FractionTest(unittest.TestCase):
96278

@@ -593,20 +775,57 @@ def testMixedArithmetic(self):
593775
self.assertTypedEquals(0.9, 1.0 - F(1, 10))
594776
self.assertTypedEquals(0.9 + 0j, (1.0 + 0j) - F(1, 10))
595777

778+
def testMixedMultiplication(self):
596779
self.assertTypedEquals(F(1, 10), F(1, 10) * 1)
597780
self.assertTypedEquals(0.1, F(1, 10) * 1.0)
598781
self.assertTypedEquals(0.1 + 0j, F(1, 10) * (1.0 + 0j))
599782
self.assertTypedEquals(F(1, 10), 1 * F(1, 10))
600783
self.assertTypedEquals(0.1, 1.0 * F(1, 10))
601784
self.assertTypedEquals(0.1 + 0j, (1.0 + 0j) * F(1, 10))
602785

786+
self.assertTypedEquals(F(3, 2) * DummyFraction(5, 3), F(5, 2))
787+
self.assertTypedEquals(DummyFraction(5, 3) * F(3, 2), F(5, 2))
788+
self.assertTypedEquals(F(3, 2) * Rat(5, 3), Rat(15, 6))
789+
self.assertTypedEquals(Rat(5, 3) * F(3, 2), F(5, 2))
790+
791+
self.assertTypedEquals(F(3, 2) * Root(4), Root(F(9, 1)))
792+
self.assertTypedEquals(Root(4) * F(3, 2), 3.0)
793+
794+
self.assertTypedEquals(F(3, 2) * Polar(4, 2), Polar(F(6, 1), 2))
795+
self.assertTypedEquals(F(3, 2) * Polar(4.0, 2), Polar(6.0, 2))
796+
self.assertTypedEquals(F(3, 2) * Rect(4, 3), Rect(F(6, 1), F(9, 2)))
797+
self.assertRaises(TypeError, operator.mul, Polar(4, 2), F(3, 2))
798+
self.assertTypedEquals(Rect(4, 3) * F(3, 2), 6.0 + 4.5j)
799+
800+
self.assertEqual(F(3, 2) * Symbolic('X'), Symbolic('3/2 * X'))
801+
self.assertRaises(TypeError, operator.mul, Symbolic('X'), F(3, 2))
802+
803+
def testMixedDivision(self):
603804
self.assertTypedEquals(F(1, 10), F(1, 10) / 1)
604805
self.assertTypedEquals(0.1, F(1, 10) / 1.0)
605806
self.assertTypedEquals(0.1 + 0j, F(1, 10) / (1.0 + 0j))
606807
self.assertTypedEquals(F(10, 1), 1 / F(1, 10))
607808
self.assertTypedEquals(10.0, 1.0 / F(1, 10))
608809
self.assertTypedEquals(10.0 + 0j, (1.0 + 0j) / F(1, 10))
609810

811+
self.assertTypedEquals(F(3, 2) / DummyFraction(3, 5), F(5, 2))
812+
self.assertTypedEquals(DummyFraction(5, 3) / F(2, 3), F(5, 2))
813+
self.assertTypedEquals(F(3, 2) / Rat(3, 5), Rat(15, 6))
814+
self.assertTypedEquals(Rat(5, 3) / F(2, 3), F(5, 2))
815+
816+
self.assertTypedEquals(F(2, 3) / Root(4), Root(F(1, 9)))
817+
self.assertTypedEquals(Root(4) / F(2, 3), 3.0)
818+
819+
self.assertTypedEquals(F(3, 2) / Polar(4, 2), Polar(F(3, 8), -2))
820+
self.assertTypedEquals(F(3, 2) / Polar(4.0, 2), Polar(0.375, -2))
821+
self.assertTypedEquals(F(3, 2) / Rect(4, 3), Rect(0.24, 0.18))
822+
self.assertRaises(TypeError, operator.truediv, Polar(4, 2), F(2, 3))
823+
self.assertTypedEquals(Rect(4, 3) / F(2, 3), 6.0 + 4.5j)
824+
825+
self.assertEqual(F(3, 2) / Symbolic('X'), Symbolic('3/2 / X'))
826+
self.assertRaises(TypeError, operator.truediv, Symbolic('X'), F(2, 3))
827+
828+
def testMixedIntegerDivision(self):
610829
self.assertTypedEquals(0, F(1, 10) // 1)
611830
self.assertTypedEquals(0.0, F(1, 10) // 1.0)
612831
self.assertTypedEquals(10, 1 // F(1, 10))
@@ -631,6 +850,21 @@ def testMixedArithmetic(self):
631850
self.assertTypedTupleEquals(divmod(-0.1, float('inf')), divmod(F(-1, 10), float('inf')))
632851
self.assertTypedTupleEquals(divmod(-0.1, float('-inf')), divmod(F(-1, 10), float('-inf')))
633852

853+
self.assertTypedEquals(F(3, 2) % DummyFraction(3, 5), F(3, 10))
854+
self.assertTypedEquals(DummyFraction(5, 3) % F(2, 3), F(1, 3))
855+
self.assertTypedEquals(F(3, 2) % Rat(3, 5), Rat(3, 6))
856+
self.assertTypedEquals(Rat(5, 3) % F(2, 3), F(1, 3))
857+
858+
self.assertRaises(TypeError, operator.mod, F(2, 3), Root(4))
859+
self.assertTypedEquals(Root(4) % F(3, 2), 0.5)
860+
861+
self.assertRaises(TypeError, operator.mod, F(3, 2), Polar(4, 2))
862+
self.assertRaises(TypeError, operator.mod, Rect(4, 3), F(2, 3))
863+
864+
self.assertEqual(F(3, 2) % Symbolic('X'), Symbolic('3/2 % X'))
865+
self.assertRaises(TypeError, operator.mod, Symbolic('X'), F(2, 3))
866+
867+
def testMixedPower(self):
634868
# ** has more interesting conversion rules.
635869
self.assertTypedEquals(F(100, 1), F(1, 10) ** -2)
636870
self.assertTypedEquals(F(100, 1), F(10, 1) ** 2)
@@ -647,6 +881,35 @@ def testMixedArithmetic(self):
647881
self.assertRaises(ZeroDivisionError, operator.pow,
648882
F(0, 1), -2)
649883

884+
self.assertTypedEquals(F(3, 2) ** Rat(3, 1), F(27, 8))
885+
self.assertTypedEquals(F(3, 2) ** Rat(-3, 1), F(8, 27))
886+
self.assertTypedEquals(F(-3, 2) ** Rat(-3, 1), F(-8, 27))
887+
self.assertTypedEquals(F(9, 4) ** Rat(3, 2), 3.375)
888+
self.assertIsInstance(F(4, 9) ** Rat(-3, 2), float)
889+
self.assertAlmostEqual(F(4, 9) ** Rat(-3, 2), 3.375)
890+
self.assertAlmostEqual(F(-4, 9) ** Rat(-3, 2), 3.375j)
891+
892+
self.assertTypedEquals(Rat(9, 4) ** F(3, 2), 3.375)
893+
self.assertTypedEquals(Rat(3, 2) ** F(3, 1), Rat(27, 8))
894+
self.assertTypedEquals(Rat(3, 2) ** F(-3, 1), F(8, 27))
895+
self.assertIsInstance(Rat(4, 9) ** F(-3, 2), float)
896+
self.assertAlmostEqual(Rat(4, 9) ** F(-3, 2), 3.375)
897+
898+
self.assertTypedEquals(Root(4) ** F(2, 3), Root(4, 3.0))
899+
self.assertTypedEquals(Root(4) ** F(2, 1), Root(4, F(1)))
900+
self.assertTypedEquals(Root(4) ** F(-2, 1), Root(4, -F(1)))
901+
self.assertTypedEquals(Root(4) ** F(-2, 3), Root(4, -3.0))
902+
903+
self.assertTypedEquals(F(3, 2) ** Rect(2, 0), Polar(2.25, 0.0))
904+
self.assertTypedEquals(F(1, 1) ** Rect(2, 3), Polar(1.0, 0.0))
905+
self.assertTypedEquals(Polar(4, 2) ** F(3, 2), Polar(8.0, 3.0))
906+
self.assertTypedEquals(Polar(4, 2) ** F(3, 1), Polar(64, 6))
907+
self.assertTypedEquals(Polar(4, 2) ** F(-3, 1), Polar(0.015625, -6))
908+
self.assertTypedEquals(Polar(4, 2) ** F(-3, 2), Polar(0.125, -3.0))
909+
910+
self.assertTypedEquals(F(3, 2) ** Symbolic('X'), Symbolic('1.5 ** X'))
911+
self.assertTypedEquals(Symbolic('X') ** F(3, 2), Symbolic('X ** 1.5'))
912+
650913
def testMixingWithDecimal(self):
651914
# Decimal refuses mixed arithmetic (but not mixed comparisons)
652915
self.assertRaises(TypeError, operator.add,

0 commit comments

Comments
 (0)