mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Update fractions from 3.14.2
This commit is contained in:
1 parent
4ca2da4987
commit
e4eb0a7116
2 files changed
+146
-22
No files matched your search
Vendored
+46
-11
@@ -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
|
||||
|
||||
Vendored
+100
-11
@@ -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()
|
||||
Reference in new issue
Block a user