mirror of https://github.com/python/cpython
gh-119189: Add more tests for mixed Fraction arithmetic (GH-119236)
This commit is contained in:
parent
034cf0c316
commit
fe67af1963
|
@ -1,5 +1,6 @@
|
|||
"""Tests for Lib/fractions.py."""
|
||||
|
||||
import cmath
|
||||
from decimal import Decimal
|
||||
from test.support import requires_IEEE_754
|
||||
import math
|
||||
|
@ -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):
|
||||
|
||||
|
@ -593,6 +775,7 @@ class FractionTest(unittest.TestCase):
|
|||
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))
|
||||
|
@ -600,6 +783,24 @@ class FractionTest(unittest.TestCase):
|
|||
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))
|
||||
|
@ -607,6 +808,24 @@ class FractionTest(unittest.TestCase):
|
|||
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))
|
||||
|
@ -631,6 +850,21 @@ class FractionTest(unittest.TestCase):
|
|||
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)
|
||||
|
@ -647,6 +881,35 @@ class FractionTest(unittest.TestCase):
|
|||
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,
|
||||
|
|
Loading…
Reference in New Issue