Skip to content

Commit

Permalink
pythongh-119189: Add more tests for mixed Fraction arithmetic (python…
Browse files Browse the repository at this point in the history
  • Loading branch information
serhiy-storchaka authored and estyxx committed Jul 17, 2024
1 parent 1f17d78 commit e065d35
Showing 1 changed file with 263 additions and 0 deletions.
263 changes: 263 additions & 0 deletions Lib/test/test_fractions.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit e065d35

Please sign in to comment.