Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.12] gh-119189: Add more tests for mixed Fraction arithmetic (GH-119236) #119256

Merged
merged 1 commit into from
May 20, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading