Update fractions from 3.14.2

This commit is contained in:
ShaharNaveh authored and Jeong, YunWon committed 2026-01-30 07:48:29 +09:00
1 parent 4ca2da4987
commit e4eb0a7116
2 files changed
+146 -22

No files matched your search

+46 -11
View File
@@ -3,7 +3,6 @@
"""Fraction, infinite-precision, rational numbers."""
from decimal import Decimal
import functools
import math
import numbers
@@ -65,7 +64,7 @@ _RATIONAL_FORMAT = re.compile(r"""
(?:\.(?P<decimal>\d*|\d+(_\d+)*))? # an optional fractional part
(?:E(?P<exp>[-+]?\d+(_\d+)*))? # and optional exponent
)
\s*\Z # and optional whitespace to finish
\s*\z # and optional whitespace to finish
""", re.VERBOSE | re.IGNORECASE)
@@ -169,9 +168,13 @@ _FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r"""
# A '0' that's *not* followed by another digit is parsed as a minimum width
# rather than a zeropad flag.
(?P<zeropad>0(?=[0-9]))?
(?P<minimumwidth>0|[1-9][0-9]*)?
(?P<minimumwidth>[0-9]+)?
(?P<thousands_sep>[,_])?
(?:\.(?P<precision>0|[1-9][0-9]*))?
(?:\.
(?=[,_0-9]) # lookahead for digit or separator
(?P<precision>[0-9]+)?
(?P<frac_separators>[,_])?
)?
(?P<presentation_type>[eEfFgG%])
""", re.DOTALL | re.VERBOSE).fullmatch
@@ -244,7 +247,9 @@ class Fraction(numbers.Rational):
self._denominator = numerator.denominator
return self
elif isinstance(numerator, (float, Decimal)):
elif (isinstance(numerator, float) or
(not isinstance(numerator, type) and
hasattr(numerator, 'as_integer_ratio'))):
# Exact conversion
self._numerator, self._denominator = numerator.as_integer_ratio()
return self
@@ -278,8 +283,8 @@ class Fraction(numbers.Rational):
numerator = -numerator
else:
raise TypeError("argument should be a string "
"or a Rational instance")
raise TypeError("argument should be a string or a Rational "
"instance or have the as_integer_ratio() method")
elif type(numerator) is int is type(denominator):
pass # *very* normal case
@@ -305,6 +310,28 @@ class Fraction(numbers.Rational):
self._denominator = denominator
return self
@classmethod
def from_number(cls, number):
"""Converts a finite real number to a rational number, exactly.
Beware that Fraction.from_number(0.3) != Fraction(3, 10).
"""
if type(number) is int:
return cls._from_coprime_ints(number, 1)
elif isinstance(number, numbers.Rational):
return cls._from_coprime_ints(number.numerator, number.denominator)
elif (isinstance(number, float) or
(not isinstance(number, type) and
hasattr(number, 'as_integer_ratio'))):
return cls._from_coprime_ints(*number.as_integer_ratio())
else:
raise TypeError("argument should be a Rational instance or "
"have the as_integer_ratio() method")
@classmethod
def from_float(cls, f):
"""Converts a finite float to a rational number, exactly.
@@ -476,6 +503,7 @@ class Fraction(numbers.Rational):
minimumwidth = int(match["minimumwidth"] or "0")
thousands_sep = match["thousands_sep"]
precision = int(match["precision"] or "6")
frac_sep = match["frac_separators"] or ""
presentation_type = match["presentation_type"]
trim_zeros = presentation_type in "gG" and not alternate_form
trim_point = not alternate_form
@@ -532,6 +560,9 @@ class Fraction(numbers.Rational):
if trim_zeros:
frac_part = frac_part.rstrip("0")
separator = "" if trim_point and not frac_part else "."
if frac_sep:
frac_part = frac_sep.join(frac_part[pos:pos + 3]
for pos in range(0, len(frac_part), 3))
trailing = separator + frac_part + suffix
# Do zero padding if required.
@@ -671,7 +702,7 @@ class Fraction(numbers.Rational):
elif isinstance(b, float):
return fallback_operator(float(a), b)
elif handle_complex and isinstance(b, complex):
return fallback_operator(complex(a), b)
return fallback_operator(float(a), b)
else:
return NotImplemented
forward.__name__ = '__' + fallback_operator.__name__ + '__'
@@ -684,7 +715,7 @@ class Fraction(numbers.Rational):
elif isinstance(a, numbers.Real):
return fallback_operator(float(a), float(b))
elif handle_complex and isinstance(a, numbers.Complex):
return fallback_operator(complex(a), complex(b))
return fallback_operator(complex(a), float(b))
else:
return NotImplemented
reverse.__name__ = '__r' + fallback_operator.__name__ + '__'
@@ -851,7 +882,7 @@ class Fraction(numbers.Rational):
__mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod, False)
def __pow__(a, b):
def __pow__(a, b, modulo=None):
"""a ** b
If b is not an integer, the result will be a float or complex
@@ -859,6 +890,8 @@ class Fraction(numbers.Rational):
result will be rational.
"""
if modulo is not None:
return NotImplemented
if isinstance(b, numbers.Rational):
if b.denominator == 1:
power = b.numerator
@@ -883,8 +916,10 @@ class Fraction(numbers.Rational):
else:
return NotImplemented
def __rpow__(b, a):
def __rpow__(b, a, modulo=None):
"""a ** b"""
if modulo is not None:
return NotImplemented
if b._denominator == 1 and b._numerator >= 0:
# If a is an int, keep it that way if possible.
return a ** b._numerator
+100 -11
View File
@@ -1,6 +1,5 @@
"""Tests for Lib/fractions.py."""
import cmath
from decimal import Decimal
from test.support import requires_IEEE_754, adjust_int_max_str_digits
import math
@@ -284,6 +283,13 @@ numbers.Complex.register(Rect)
class RectComplex(Rect, complex):
pass
class Ratio:
def __init__(self, ratio):
self._ratio = ratio
def as_integer_ratio(self):
return self._ratio
class FractionTest(unittest.TestCase):
def assertTypedEquals(self, expected, actual):
@@ -355,6 +361,38 @@ class FractionTest(unittest.TestCase):
self.assertRaises(OverflowError, F, Decimal('inf'))
self.assertRaises(OverflowError, F, Decimal('-inf'))
def testInitFromIntegerRatio(self):
self.assertEqual((7, 3), _components(F(Ratio((7, 3)))))
errmsg = (r"argument should be a string or a Rational instance or "
r"have the as_integer_ratio\(\) method")
# the type also has an "as_integer_ratio" attribute.
self.assertRaisesRegex(TypeError, errmsg, F, Ratio)
# bad ratio
self.assertRaises(TypeError, F, Ratio(7))
self.assertRaises(ValueError, F, Ratio((7,)))
self.assertRaises(ValueError, F, Ratio((7, 3, 1)))
# only single-argument form
self.assertRaises(TypeError, F, Ratio((3, 7)), 11)
self.assertRaises(TypeError, F, 2, Ratio((-10, 9)))
# as_integer_ratio not defined in a class
class A:
pass
a = A()
a.as_integer_ratio = lambda: (9, 5)
self.assertEqual((9, 5), _components(F(a)))
# as_integer_ratio defined in a metaclass
class M(type):
def as_integer_ratio(self):
return (11, 9)
class B(metaclass=M):
pass
self.assertRaisesRegex(TypeError, errmsg, F, B)
self.assertRaisesRegex(TypeError, errmsg, F, B())
self.assertRaises(TypeError, F.from_number, B)
self.assertRaises(TypeError, F.from_number, B())
def testFromString(self):
self.assertEqual((5, 1), _components(F("5")))
self.assertEqual((5, 1), _components(F("005")))
@@ -548,6 +586,37 @@ class FractionTest(unittest.TestCase):
ValueError, "cannot convert NaN to integer ratio",
F.from_decimal, Decimal("snan"))
def testFromNumber(self, cls=F):
def check(arg, numerator, denominator):
f = cls.from_number(arg)
self.assertIs(type(f), cls)
self.assertEqual(f.numerator, numerator)
self.assertEqual(f.denominator, denominator)
check(10, 10, 1)
check(2.5, 5, 2)
check(Decimal('2.5'), 5, 2)
check(F(22, 7), 22, 7)
check(DummyFraction(22, 7), 22, 7)
check(Rat(22, 7), 22, 7)
check(Ratio((22, 7)), 22, 7)
self.assertRaises(TypeError, cls.from_number, 3+4j)
self.assertRaises(TypeError, cls.from_number, '5/2')
self.assertRaises(TypeError, cls.from_number, [])
self.assertRaises(OverflowError, cls.from_number, float('inf'))
self.assertRaises(OverflowError, cls.from_number, Decimal('inf'))
# as_integer_ratio not defined in a class
class A:
pass
a = A()
a.as_integer_ratio = lambda: (9, 5)
check(a, 9, 5)
def testFromNumber_subclass(self):
self.testFromNumber(DummyFraction)
def test_is_integer(self):
self.assertTrue(F(1, 1).is_integer())
self.assertTrue(F(-1, 1).is_integer())
@@ -794,7 +863,7 @@ class FractionTest(unittest.TestCase):
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.assertTypedEquals(F(3, 2) * RectComplex(4, 3), RectComplex(6.0+0j, 4.5+0j))
self.assertTypedEquals(F(3, 2) * RectComplex(4, 3), RectComplex(6.0, 4.5))
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) * SymbolicComplex('X'), SymbolicComplex('3/2 * X'))
@@ -1253,6 +1322,8 @@ class FractionTest(unittest.TestCase):
# Thousands separators
(F('1234567.123456'), ',.5e', '1.23457e+06'),
(F('123.123456'), '012_.2e', '0_001.23e+02'),
# Thousands separators for fractional part (or for integral too)
(F('1234567.123456'), '.5_e', '1.234_57e+06'),
# z flag is legal, but never makes a difference to the output
(F(-1, 7**100), 'z.6e', '-3.091690e-85'),
]
@@ -1378,6 +1449,12 @@ class FractionTest(unittest.TestCase):
(F('1234567'), ',.2f', '1,234,567.00'),
(F('12345678'), ',.2f', '12,345,678.00'),
(F('12345678'), ',f', '12,345,678.000000'),
# Thousands separators for fractional part (or for integral too)
(F('123456.789123123'), '._f', '123456.789_123'),
(F('123456.789123123'), '.7_f', '123456.789_123_1'),
(F('123456.789123123'), '.9_f', '123456.789_123_123'),
(F('123456.789123123'), '.,f', '123456.789,123'),
(F('123456.789123123'), '_.,f', '123_456.789,123'),
# Underscore as thousands separator
(F(2, 3), '_.2f', '0.67'),
(F(2, 3), '_.7f', '0.6666667'),
@@ -1446,6 +1523,8 @@ class FractionTest(unittest.TestCase):
(F(51, 1000), '.1f', '0.1'),
(F(149, 1000), '.1f', '0.1'),
(F(151, 1000), '.1f', '0.2'),
(F(22, 7), '.02f', '3.14'), # issue gh-130662
(F(22, 7), '005.02f', '03.14'),
]
for fraction, spec, expected in testcases:
with self.subTest(fraction=fraction, spec=spec):
@@ -1544,17 +1623,16 @@ class FractionTest(unittest.TestCase):
'=010%',
'>00.2f',
'>00f',
# Too many zeros - minimum width should not have leading zeros
'006f',
# Leading zeros in precision
'.010f',
'.02f',
'.000f',
# Missing precision
'.e',
'.f',
'.g',
'.%',
# Thousands separators before precision
'._6e',
'._6f',
'._6g',
'._6%',
# Z instead of z for negative zero suppression
'Z.2f'
# z flag not supported for general formatting
@@ -1591,9 +1669,7 @@ class FractionTest(unittest.TestCase):
self.assertEqual(float(format(f, fmt2)), float(rhs))
self.assertEqual(float(format(-f, fmt2)), float('-' + rhs))
# TODO: RUSTPYTHON
# TypeError: '%' not supported between instances of 'Fraction' and 'complex'
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: '%' not supported between instances of 'Fraction' and 'complex'
def test_complex_handling(self):
# See issue gh-102840 for more details.
@@ -1621,6 +1697,19 @@ class FractionTest(unittest.TestCase):
message % ("divmod()", "complex", "Fraction"),
divmod, b, a)
@unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message
def test_three_argument_pow(self):
message = "unsupported operand type(s) for ** or pow(): '%s', '%s', '%s'"
self.assertRaisesMessage(TypeError,
message % ("Fraction", "int", "int"),
pow, F(3), 4, 5)
self.assertRaisesMessage(TypeError,
message % ("int", "Fraction", "int"),
pow, 3, F(4), 5)
self.assertRaisesMessage(TypeError,
message % ("int", "int", "Fraction"),
pow, 3, 4, F(5))
if __name__ == '__main__':
unittest.main()