@@ -1,5 +1,6 @@ """Tests for Lib/fractions.py.""" import cmath from decimal import Decimal from test.support import requires_IEEE_754 import math Expand Down Expand Up @@ -91,6 +92,187 @@ class DummyFraction(fractions.Fraction): def _components(r): return (r.numerator, r.denominator) def typed_approx_eq(a, b): return type(a) == type(b) and (a == b or math.isclose(a, b)) class Symbolic: """Simple non-numeric class for testing mixed arithmetic. It is not Integral, Rational, Real or Complex, and cannot be conveted to int, float or complex. but it supports some arithmetic operations. """ def __init__(self, value): self.value = value def __mul__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(f'{self} * {other}') def __rmul__(self, other): return self.__class__(f'{other} * {self}') def __truediv__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(f'{self} / {other}') def __rtruediv__(self, other): return self.__class__(f'{other} / {self}') def __mod__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(f'{self} % {other}') def __rmod__(self, other): return self.__class__(f'{other} % {self}') def __pow__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(f'{self} ** {other}') def __rpow__(self, other): return self.__class__(f'{other} ** {self}') def __eq__(self, other): if other.__class__ != self.__class__: return NotImplemented return self.value == other.value def __str__(self): return f'{self.value}' def __repr__(self): return f'{self.__class__.__name__}({self.value!r})' class Rat: """Simple Rational class for testing mixed arithmetic.""" def __init__(self, n, d): self.numerator = n self.denominator = d def __mul__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(self.numerator * other.numerator, self.denominator * other.denominator) def __rmul__(self, other): return self.__class__(other.numerator * self.numerator, other.denominator * self.denominator) def __truediv__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(self.numerator * other.denominator, self.denominator * other.numerator) def __rtruediv__(self, other): return self.__class__(other.numerator * self.denominator, other.denominator * self.numerator) def __mod__(self, other): if isinstance(other, F): return NotImplemented d = self.denominator * other.numerator return self.__class__(self.numerator * other.denominator % d, d) def __rmod__(self, other): d = other.denominator * self.numerator return self.__class__(other.numerator * self.denominator % d, d) return self.__class__(other.numerator / self.numerator, other.denominator / self.denominator) def __pow__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(self.numerator ** other, self.denominator ** other) def __float__(self): return self.numerator / self.denominator def __eq__(self, other): if self.__class__ != other.__class__: return NotImplemented return (typed_approx_eq(self.numerator, other.numerator) and typed_approx_eq(self.denominator, other.denominator)) def __repr__(self): return f'{self.__class__.__name__}({self.numerator!r}, {self.denominator!r})' numbers.Rational.register(Rat) class Root: """Simple Real class for testing mixed arithmetic.""" def __init__(self, v, n=F(2)): self.base = v self.degree = n def __mul__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(self.base * other**self.degree, self.degree) def __rmul__(self, other): return self.__class__(other**self.degree * self.base, self.degree) def __truediv__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(self.base / other**self.degree, self.degree) def __rtruediv__(self, other): return self.__class__(other**self.degree / self.base, self.degree) def __pow__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(self.base, self.degree / other) def __float__(self): return float(self.base) ** (1 / float(self.degree)) def __eq__(self, other): if self.__class__ != other.__class__: return NotImplemented return typed_approx_eq(self.base, other.base) and typed_approx_eq(self.degree, other.degree) def __repr__(self): return f'{self.__class__.__name__}({self.base!r}, {self.degree!r})' numbers.Real.register(Root) class Polar: """Simple Complex class for testing mixed arithmetic.""" def __init__(self, r, phi): self.r = r self.phi = phi def __mul__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(self.r * other, self.phi) def __rmul__(self, other): return self.__class__(other * self.r, self.phi) def __truediv__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(self.r / other, self.phi) def __rtruediv__(self, other): return self.__class__(other / self.r, -self.phi) def __pow__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(self.r ** other, self.phi * other) def __eq__(self, other): if self.__class__ != other.__class__: return NotImplemented return typed_approx_eq(self.r, other.r) and typed_approx_eq(self.phi, other.phi) def __repr__(self): return f'{self.__class__.__name__}({self.r!r}, {self.phi!r})' numbers.Complex.register(Polar) class Rect: """Other simple Complex class for testing mixed arithmetic.""" def __init__(self, x, y): self.x = x self.y = y def __mul__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(self.x * other, self.y * other) def __rmul__(self, other): return self.__class__(other * self.x, other * self.y) def __truediv__(self, other): if isinstance(other, F): return NotImplemented return self.__class__(self.x / other, self.y / other) def __rtruediv__(self, other): r = self.x * self.x + self.y * self.y return self.__class__(other * (self.x / r), other * (self.y / r)) def __rpow__(self, other): return Polar(other ** self.x, math.log(other) * self.y) def __complex__(self): return complex(self.x, self.y) def __eq__(self, other): if self.__class__ != other.__class__: return NotImplemented return typed_approx_eq(self.x, other.x) and typed_approx_eq(self.y, other.y) def __repr__(self): return f'{self.__class__.__name__}({self.x!r}, {self.y!r})' numbers.Complex.register(Rect) class FractionTest(unittest.TestCase): Expand Down Expand Up @@ -593,20 +775,57 @@ def testMixedArithmetic(self): self.assertTypedEquals(0.9, 1.0 - F(1, 10)) self.assertTypedEquals(0.9 + 0j, (1.0 + 0j) - F(1, 10)) def testMixedMultiplication(self): self.assertTypedEquals(F(1, 10), F(1, 10) * 1) self.assertTypedEquals(0.1, F(1, 10) * 1.0) self.assertTypedEquals(0.1 + 0j, F(1, 10) * (1.0 + 0j)) self.assertTypedEquals(F(1, 10), 1 * F(1, 10)) self.assertTypedEquals(0.1, 1.0 * F(1, 10)) self.assertTypedEquals(0.1 + 0j, (1.0 + 0j) * F(1, 10)) self.assertTypedEquals(F(3, 2) * DummyFraction(5, 3), F(5, 2)) self.assertTypedEquals(DummyFraction(5, 3) * F(3, 2), F(5, 2)) self.assertTypedEquals(F(3, 2) * Rat(5, 3), Rat(15, 6)) self.assertTypedEquals(Rat(5, 3) * F(3, 2), F(5, 2)) self.assertTypedEquals(F(3, 2) * Root(4), Root(F(9, 1))) self.assertTypedEquals(Root(4) * F(3, 2), 3.0) self.assertTypedEquals(F(3, 2) * Polar(4, 2), Polar(F(6, 1), 2)) self.assertTypedEquals(F(3, 2) * Polar(4.0, 2), Polar(6.0, 2)) self.assertTypedEquals(F(3, 2) * Rect(4, 3), Rect(F(6, 1), F(9, 2))) self.assertRaises(TypeError, operator.mul, Polar(4, 2), F(3, 2)) self.assertTypedEquals(Rect(4, 3) * F(3, 2), 6.0 + 4.5j) self.assertEqual(F(3, 2) * Symbolic('X'), Symbolic('3/2 * X')) self.assertRaises(TypeError, operator.mul, Symbolic('X'), F(3, 2)) def testMixedDivision(self): self.assertTypedEquals(F(1, 10), F(1, 10) / 1) self.assertTypedEquals(0.1, F(1, 10) / 1.0) self.assertTypedEquals(0.1 + 0j, F(1, 10) / (1.0 + 0j)) self.assertTypedEquals(F(10, 1), 1 / F(1, 10)) self.assertTypedEquals(10.0, 1.0 / F(1, 10)) self.assertTypedEquals(10.0 + 0j, (1.0 + 0j) / F(1, 10)) self.assertTypedEquals(F(3, 2) / DummyFraction(3, 5), F(5, 2)) self.assertTypedEquals(DummyFraction(5, 3) / F(2, 3), F(5, 2)) self.assertTypedEquals(F(3, 2) / Rat(3, 5), Rat(15, 6)) self.assertTypedEquals(Rat(5, 3) / F(2, 3), F(5, 2)) self.assertTypedEquals(F(2, 3) / Root(4), Root(F(1, 9))) self.assertTypedEquals(Root(4) / F(2, 3), 3.0) self.assertTypedEquals(F(3, 2) / Polar(4, 2), Polar(F(3, 8), -2)) self.assertTypedEquals(F(3, 2) / Polar(4.0, 2), Polar(0.375, -2)) self.assertTypedEquals(F(3, 2) / Rect(4, 3), Rect(0.24, 0.18)) self.assertRaises(TypeError, operator.truediv, Polar(4, 2), F(2, 3)) self.assertTypedEquals(Rect(4, 3) / F(2, 3), 6.0 + 4.5j) self.assertEqual(F(3, 2) / Symbolic('X'), Symbolic('3/2 / X')) self.assertRaises(TypeError, operator.truediv, Symbolic('X'), F(2, 3)) def testMixedIntegerDivision(self): self.assertTypedEquals(0, F(1, 10) // 1) self.assertTypedEquals(0.0, F(1, 10) // 1.0) self.assertTypedEquals(10, 1 // F(1, 10)) Expand All @@ -631,6 +850,21 @@ def testMixedArithmetic(self): self.assertTypedTupleEquals(divmod(-0.1, float('inf')), divmod(F(-1, 10), float('inf'))) self.assertTypedTupleEquals(divmod(-0.1, float('-inf')), divmod(F(-1, 10), float('-inf'))) self.assertTypedEquals(F(3, 2) % DummyFraction(3, 5), F(3, 10)) self.assertTypedEquals(DummyFraction(5, 3) % F(2, 3), F(1, 3)) self.assertTypedEquals(F(3, 2) % Rat(3, 5), Rat(3, 6)) self.assertTypedEquals(Rat(5, 3) % F(2, 3), F(1, 3)) self.assertRaises(TypeError, operator.mod, F(2, 3), Root(4)) self.assertTypedEquals(Root(4) % F(3, 2), 0.5) self.assertRaises(TypeError, operator.mod, F(3, 2), Polar(4, 2)) self.assertRaises(TypeError, operator.mod, Rect(4, 3), F(2, 3)) self.assertEqual(F(3, 2) % Symbolic('X'), Symbolic('3/2 % X')) self.assertRaises(TypeError, operator.mod, Symbolic('X'), F(2, 3)) def testMixedPower(self): # ** has more interesting conversion rules. self.assertTypedEquals(F(100, 1), F(1, 10) ** -2) self.assertTypedEquals(F(100, 1), F(10, 1) ** 2) Expand All @@ -647,6 +881,35 @@ def testMixedArithmetic(self): self.assertRaises(ZeroDivisionError, operator.pow, F(0, 1), -2) self.assertTypedEquals(F(3, 2) ** Rat(3, 1), F(27, 8)) self.assertTypedEquals(F(3, 2) ** Rat(-3, 1), F(8, 27)) self.assertTypedEquals(F(-3, 2) ** Rat(-3, 1), F(-8, 27)) self.assertTypedEquals(F(9, 4) ** Rat(3, 2), 3.375) self.assertIsInstance(F(4, 9) ** Rat(-3, 2), float) self.assertAlmostEqual(F(4, 9) ** Rat(-3, 2), 3.375) self.assertAlmostEqual(F(-4, 9) ** Rat(-3, 2), 3.375j) self.assertTypedEquals(Rat(9, 4) ** F(3, 2), 3.375) self.assertTypedEquals(Rat(3, 2) ** F(3, 1), Rat(27, 8)) self.assertTypedEquals(Rat(3, 2) ** F(-3, 1), F(8, 27)) self.assertIsInstance(Rat(4, 9) ** F(-3, 2), float) self.assertAlmostEqual(Rat(4, 9) ** F(-3, 2), 3.375) self.assertTypedEquals(Root(4) ** F(2, 3), Root(4, 3.0)) self.assertTypedEquals(Root(4) ** F(2, 1), Root(4, F(1))) self.assertTypedEquals(Root(4) ** F(-2, 1), Root(4, -F(1))) self.assertTypedEquals(Root(4) ** F(-2, 3), Root(4, -3.0)) self.assertTypedEquals(F(3, 2) ** Rect(2, 0), Polar(2.25, 0.0)) self.assertTypedEquals(F(1, 1) ** Rect(2, 3), Polar(1.0, 0.0)) self.assertTypedEquals(Polar(4, 2) ** F(3, 2), Polar(8.0, 3.0)) self.assertTypedEquals(Polar(4, 2) ** F(3, 1), Polar(64, 6)) self.assertTypedEquals(Polar(4, 2) ** F(-3, 1), Polar(0.015625, -6)) self.assertTypedEquals(Polar(4, 2) ** F(-3, 2), Polar(0.125, -3.0)) self.assertTypedEquals(F(3, 2) ** Symbolic('X'), Symbolic('1.5 ** X')) self.assertTypedEquals(Symbolic('X') ** F(3, 2), Symbolic('X ** 1.5')) def testMixingWithDecimal(self): # Decimal refuses mixed arithmetic (but not mixed comparisons) self.assertRaises(TypeError, operator.add, Expand Down