mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Update fraction,test_numeric_tower from CPython 3.12.4
This commit is contained in:
1 parent
694354e5e6
commit
09d74336fa
3 files changed
+1065
-99
No files matched your search
Vendored
+326
-94
@@ -4,6 +4,7 @@
|
||||
"""Fraction, infinite-precision, rational numbers."""
|
||||
|
||||
from decimal import Decimal
|
||||
import functools
|
||||
import math
|
||||
import numbers
|
||||
import operator
|
||||
@@ -20,21 +21,144 @@ _PyHASH_MODULUS = sys.hash_info.modulus
|
||||
# _PyHASH_MODULUS.
|
||||
_PyHASH_INF = sys.hash_info.inf
|
||||
|
||||
@functools.lru_cache(maxsize = 1 << 14)
|
||||
def _hash_algorithm(numerator, denominator):
|
||||
|
||||
# To make sure that the hash of a Fraction agrees with the hash
|
||||
# of a numerically equal integer, float or Decimal instance, we
|
||||
# follow the rules for numeric hashes outlined in the
|
||||
# documentation. (See library docs, 'Built-in Types').
|
||||
|
||||
try:
|
||||
dinv = pow(denominator, -1, _PyHASH_MODULUS)
|
||||
except ValueError:
|
||||
# ValueError means there is no modular inverse.
|
||||
hash_ = _PyHASH_INF
|
||||
else:
|
||||
# The general algorithm now specifies that the absolute value of
|
||||
# the hash is
|
||||
# (|N| * dinv) % P
|
||||
# where N is self._numerator and P is _PyHASH_MODULUS. That's
|
||||
# optimized here in two ways: first, for a non-negative int i,
|
||||
# hash(i) == i % P, but the int hash implementation doesn't need
|
||||
# to divide, and is faster than doing % P explicitly. So we do
|
||||
# hash(|N| * dinv)
|
||||
# instead. Second, N is unbounded, so its product with dinv may
|
||||
# be arbitrarily expensive to compute. The final answer is the
|
||||
# same if we use the bounded |N| % P instead, which can again
|
||||
# be done with an int hash() call. If 0 <= i < P, hash(i) == i,
|
||||
# so this nested hash() call wastes a bit of time making a
|
||||
# redundant copy when |N| < P, but can save an arbitrarily large
|
||||
# amount of computation for large |N|.
|
||||
hash_ = hash(hash(abs(numerator)) * dinv)
|
||||
result = hash_ if numerator >= 0 else -hash_
|
||||
return -2 if result == -1 else result
|
||||
|
||||
_RATIONAL_FORMAT = re.compile(r"""
|
||||
\A\s* # optional whitespace at the start,
|
||||
(?P<sign>[-+]?) # an optional sign, then
|
||||
(?=\d|\.\d) # lookahead for digit or .digit
|
||||
(?P<num>\d*|\d+(_\d+)*) # numerator (possibly empty)
|
||||
(?: # followed by
|
||||
(?:/(?P<denom>\d+(_\d+)*))? # an optional denominator
|
||||
| # or
|
||||
(?:\.(?P<decimal>d*|\d+(_\d+)*))? # an optional fractional part
|
||||
(?:E(?P<exp>[-+]?\d+(_\d+)*))? # and optional exponent
|
||||
\A\s* # optional whitespace at the start,
|
||||
(?P<sign>[-+]?) # an optional sign, then
|
||||
(?=\d|\.\d) # lookahead for digit or .digit
|
||||
(?P<num>\d*|\d+(_\d+)*) # numerator (possibly empty)
|
||||
(?: # followed by
|
||||
(?:\s*/\s*(?P<denom>\d+(_\d+)*))? # an optional denominator
|
||||
| # or
|
||||
(?:\.(?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)
|
||||
|
||||
|
||||
# Helpers for formatting
|
||||
|
||||
def _round_to_exponent(n, d, exponent, no_neg_zero=False):
|
||||
"""Round a rational number to the nearest multiple of a given power of 10.
|
||||
|
||||
Rounds the rational number n/d to the nearest integer multiple of
|
||||
10**exponent, rounding to the nearest even integer multiple in the case of
|
||||
a tie. Returns a pair (sign: bool, significand: int) representing the
|
||||
rounded value (-1)**sign * significand * 10**exponent.
|
||||
|
||||
If no_neg_zero is true, then the returned sign will always be False when
|
||||
the significand is zero. Otherwise, the sign reflects the sign of the
|
||||
input.
|
||||
|
||||
d must be positive, but n and d need not be relatively prime.
|
||||
"""
|
||||
if exponent >= 0:
|
||||
d *= 10**exponent
|
||||
else:
|
||||
n *= 10**-exponent
|
||||
|
||||
# The divmod quotient is correct for round-ties-towards-positive-infinity;
|
||||
# In the case of a tie, we zero out the least significant bit of q.
|
||||
q, r = divmod(n + (d >> 1), d)
|
||||
if r == 0 and d & 1 == 0:
|
||||
q &= -2
|
||||
|
||||
sign = q < 0 if no_neg_zero else n < 0
|
||||
return sign, abs(q)
|
||||
|
||||
|
||||
def _round_to_figures(n, d, figures):
|
||||
"""Round a rational number to a given number of significant figures.
|
||||
|
||||
Rounds the rational number n/d to the given number of significant figures
|
||||
using the round-ties-to-even rule, and returns a triple
|
||||
(sign: bool, significand: int, exponent: int) representing the rounded
|
||||
value (-1)**sign * significand * 10**exponent.
|
||||
|
||||
In the special case where n = 0, returns a significand of zero and
|
||||
an exponent of 1 - figures, for compatibility with formatting.
|
||||
Otherwise, the returned significand satisfies
|
||||
10**(figures - 1) <= significand < 10**figures.
|
||||
|
||||
d must be positive, but n and d need not be relatively prime.
|
||||
figures must be positive.
|
||||
"""
|
||||
# Special case for n == 0.
|
||||
if n == 0:
|
||||
return False, 0, 1 - figures
|
||||
|
||||
# Find integer m satisfying 10**(m - 1) <= abs(n)/d <= 10**m. (If abs(n)/d
|
||||
# is a power of 10, either of the two possible values for m is fine.)
|
||||
str_n, str_d = str(abs(n)), str(d)
|
||||
m = len(str_n) - len(str_d) + (str_d <= str_n)
|
||||
|
||||
# Round to a multiple of 10**(m - figures). The significand we get
|
||||
# satisfies 10**(figures - 1) <= significand <= 10**figures.
|
||||
exponent = m - figures
|
||||
sign, significand = _round_to_exponent(n, d, exponent)
|
||||
|
||||
# Adjust in the case where significand == 10**figures, to ensure that
|
||||
# 10**(figures - 1) <= significand < 10**figures.
|
||||
if len(str(significand)) == figures + 1:
|
||||
significand //= 10
|
||||
exponent += 1
|
||||
|
||||
return sign, significand, exponent
|
||||
|
||||
|
||||
# Pattern for matching float-style format specifications;
|
||||
# supports 'e', 'E', 'f', 'F', 'g', 'G' and '%' presentation types.
|
||||
_FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r"""
|
||||
(?:
|
||||
(?P<fill>.)?
|
||||
(?P<align>[<>=^])
|
||||
)?
|
||||
(?P<sign>[-+ ]?)
|
||||
(?P<no_neg_zero>z)?
|
||||
(?P<alt>\#)?
|
||||
# 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<thousands_sep>[,_])?
|
||||
(?:\.(?P<precision>0|[1-9][0-9]*))?
|
||||
(?P<presentation_type>[eEfFgG%])
|
||||
""", re.DOTALL | re.VERBOSE).fullmatch
|
||||
|
||||
|
||||
class Fraction(numbers.Rational):
|
||||
"""This class implements rational numbers.
|
||||
|
||||
@@ -59,7 +183,7 @@ class Fraction(numbers.Rational):
|
||||
__slots__ = ('_numerator', '_denominator')
|
||||
|
||||
# We're immutable, so use __new__ not __init__
|
||||
def __new__(cls, numerator=0, denominator=None, *, _normalize=True):
|
||||
def __new__(cls, numerator=0, denominator=None):
|
||||
"""Constructs a Rational.
|
||||
|
||||
Takes a string like '3/2' or '1.5', another Rational instance, a
|
||||
@@ -155,12 +279,11 @@ class Fraction(numbers.Rational):
|
||||
|
||||
if denominator == 0:
|
||||
raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
|
||||
if _normalize:
|
||||
g = math.gcd(numerator, denominator)
|
||||
if denominator < 0:
|
||||
g = -g
|
||||
numerator //= g
|
||||
denominator //= g
|
||||
g = math.gcd(numerator, denominator)
|
||||
if denominator < 0:
|
||||
g = -g
|
||||
numerator //= g
|
||||
denominator //= g
|
||||
self._numerator = numerator
|
||||
self._denominator = denominator
|
||||
return self
|
||||
@@ -177,7 +300,7 @@ class Fraction(numbers.Rational):
|
||||
elif not isinstance(f, float):
|
||||
raise TypeError("%s.from_float() only takes floats, not %r (%s)" %
|
||||
(cls.__name__, f, type(f).__name__))
|
||||
return cls(*f.as_integer_ratio())
|
||||
return cls._from_coprime_ints(*f.as_integer_ratio())
|
||||
|
||||
@classmethod
|
||||
def from_decimal(cls, dec):
|
||||
@@ -189,13 +312,28 @@ class Fraction(numbers.Rational):
|
||||
raise TypeError(
|
||||
"%s.from_decimal() only takes Decimals, not %r (%s)" %
|
||||
(cls.__name__, dec, type(dec).__name__))
|
||||
return cls(*dec.as_integer_ratio())
|
||||
return cls._from_coprime_ints(*dec.as_integer_ratio())
|
||||
|
||||
@classmethod
|
||||
def _from_coprime_ints(cls, numerator, denominator, /):
|
||||
"""Convert a pair of ints to a rational number, for internal use.
|
||||
|
||||
The ratio of integers should be in lowest terms and the denominator
|
||||
should be positive.
|
||||
"""
|
||||
obj = super(Fraction, cls).__new__(cls)
|
||||
obj._numerator = numerator
|
||||
obj._denominator = denominator
|
||||
return obj
|
||||
|
||||
def is_integer(self):
|
||||
"""Return True if the Fraction is an integer."""
|
||||
return self._denominator == 1
|
||||
|
||||
def as_integer_ratio(self):
|
||||
"""Return the integer ratio as a tuple.
|
||||
"""Return a pair of integers, whose ratio is equal to the original Fraction.
|
||||
|
||||
Return a tuple of two integers, whose ratio is equal to the
|
||||
Fraction and with a positive denominator.
|
||||
The ratio is in lowest terms and has a positive denominator.
|
||||
"""
|
||||
return (self._numerator, self._denominator)
|
||||
|
||||
@@ -245,14 +383,16 @@ class Fraction(numbers.Rational):
|
||||
break
|
||||
p0, q0, p1, q1 = p1, q1, p0+a*p1, q2
|
||||
n, d = d, n-a*d
|
||||
|
||||
k = (max_denominator-q0)//q1
|
||||
bound1 = Fraction(p0+k*p1, q0+k*q1)
|
||||
bound2 = Fraction(p1, q1)
|
||||
if abs(bound2 - self) <= abs(bound1-self):
|
||||
return bound2
|
||||
|
||||
# Determine which of the candidates (p0+k*p1)/(q0+k*q1) and p1/q1 is
|
||||
# closer to self. The distance between them is 1/(q1*(q0+k*q1)), while
|
||||
# the distance from p1/q1 to self is d/(q1*self._denominator). So we
|
||||
# need to compare 2*(q0+k*q1) with self._denominator/d.
|
||||
if 2*d*(q0+k*q1) <= self._denominator:
|
||||
return Fraction._from_coprime_ints(p1, q1)
|
||||
else:
|
||||
return bound1
|
||||
return Fraction._from_coprime_ints(p0+k*p1, q0+k*q1)
|
||||
|
||||
@property
|
||||
def numerator(a):
|
||||
@@ -274,6 +414,122 @@ class Fraction(numbers.Rational):
|
||||
else:
|
||||
return '%s/%s' % (self._numerator, self._denominator)
|
||||
|
||||
def __format__(self, format_spec, /):
|
||||
"""Format this fraction according to the given format specification."""
|
||||
|
||||
# Backwards compatiblility with existing formatting.
|
||||
if not format_spec:
|
||||
return str(self)
|
||||
|
||||
# Validate and parse the format specifier.
|
||||
match = _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec)
|
||||
if match is None:
|
||||
raise ValueError(
|
||||
f"Invalid format specifier {format_spec!r} "
|
||||
f"for object of type {type(self).__name__!r}"
|
||||
)
|
||||
elif match["align"] is not None and match["zeropad"] is not None:
|
||||
# Avoid the temptation to guess.
|
||||
raise ValueError(
|
||||
f"Invalid format specifier {format_spec!r} "
|
||||
f"for object of type {type(self).__name__!r}; "
|
||||
"can't use explicit alignment when zero-padding"
|
||||
)
|
||||
fill = match["fill"] or " "
|
||||
align = match["align"] or ">"
|
||||
pos_sign = "" if match["sign"] == "-" else match["sign"]
|
||||
no_neg_zero = bool(match["no_neg_zero"])
|
||||
alternate_form = bool(match["alt"])
|
||||
zeropad = bool(match["zeropad"])
|
||||
minimumwidth = int(match["minimumwidth"] or "0")
|
||||
thousands_sep = match["thousands_sep"]
|
||||
precision = int(match["precision"] or "6")
|
||||
presentation_type = match["presentation_type"]
|
||||
trim_zeros = presentation_type in "gG" and not alternate_form
|
||||
trim_point = not alternate_form
|
||||
exponent_indicator = "E" if presentation_type in "EFG" else "e"
|
||||
|
||||
# Round to get the digits we need, figure out where to place the point,
|
||||
# and decide whether to use scientific notation. 'point_pos' is the
|
||||
# relative to the _end_ of the digit string: that is, it's the number
|
||||
# of digits that should follow the point.
|
||||
if presentation_type in "fF%":
|
||||
exponent = -precision
|
||||
if presentation_type == "%":
|
||||
exponent -= 2
|
||||
negative, significand = _round_to_exponent(
|
||||
self._numerator, self._denominator, exponent, no_neg_zero)
|
||||
scientific = False
|
||||
point_pos = precision
|
||||
else: # presentation_type in "eEgG"
|
||||
figures = (
|
||||
max(precision, 1)
|
||||
if presentation_type in "gG"
|
||||
else precision + 1
|
||||
)
|
||||
negative, significand, exponent = _round_to_figures(
|
||||
self._numerator, self._denominator, figures)
|
||||
scientific = (
|
||||
presentation_type in "eE"
|
||||
or exponent > 0
|
||||
or exponent + figures <= -4
|
||||
)
|
||||
point_pos = figures - 1 if scientific else -exponent
|
||||
|
||||
# Get the suffix - the part following the digits, if any.
|
||||
if presentation_type == "%":
|
||||
suffix = "%"
|
||||
elif scientific:
|
||||
suffix = f"{exponent_indicator}{exponent + point_pos:+03d}"
|
||||
else:
|
||||
suffix = ""
|
||||
|
||||
# String of output digits, padded sufficiently with zeros on the left
|
||||
# so that we'll have at least one digit before the decimal point.
|
||||
digits = f"{significand:0{point_pos + 1}d}"
|
||||
|
||||
# Before padding, the output has the form f"{sign}{leading}{trailing}",
|
||||
# where `leading` includes thousands separators if necessary and
|
||||
# `trailing` includes the decimal separator where appropriate.
|
||||
sign = "-" if negative else pos_sign
|
||||
leading = digits[: len(digits) - point_pos]
|
||||
frac_part = digits[len(digits) - point_pos :]
|
||||
if trim_zeros:
|
||||
frac_part = frac_part.rstrip("0")
|
||||
separator = "" if trim_point and not frac_part else "."
|
||||
trailing = separator + frac_part + suffix
|
||||
|
||||
# Do zero padding if required.
|
||||
if zeropad:
|
||||
min_leading = minimumwidth - len(sign) - len(trailing)
|
||||
# When adding thousands separators, they'll be added to the
|
||||
# zero-padded portion too, so we need to compensate.
|
||||
leading = leading.zfill(
|
||||
3 * min_leading // 4 + 1 if thousands_sep else min_leading
|
||||
)
|
||||
|
||||
# Insert thousands separators if required.
|
||||
if thousands_sep:
|
||||
first_pos = 1 + (len(leading) - 1) % 3
|
||||
leading = leading[:first_pos] + "".join(
|
||||
thousands_sep + leading[pos : pos + 3]
|
||||
for pos in range(first_pos, len(leading), 3)
|
||||
)
|
||||
|
||||
# We now have a sign and a body. Pad with fill character if necessary
|
||||
# and return.
|
||||
body = leading + trailing
|
||||
padding = fill * (minimumwidth - len(sign) - len(body))
|
||||
if align == ">":
|
||||
return padding + sign + body
|
||||
elif align == "<":
|
||||
return sign + body + padding
|
||||
elif align == "^":
|
||||
half = len(padding) // 2
|
||||
return padding[:half] + sign + body + padding[half:]
|
||||
else: # align == "="
|
||||
return sign + padding + body
|
||||
|
||||
def _operator_fallbacks(monomorphic_operator, fallback_operator):
|
||||
"""Generates forward and reverse operators given a purely-rational
|
||||
operator and a function from the operator module.
|
||||
@@ -355,8 +611,10 @@ class Fraction(numbers.Rational):
|
||||
|
||||
"""
|
||||
def forward(a, b):
|
||||
if isinstance(b, (int, Fraction)):
|
||||
if isinstance(b, Fraction):
|
||||
return monomorphic_operator(a, b)
|
||||
elif isinstance(b, int):
|
||||
return monomorphic_operator(a, Fraction(b))
|
||||
elif isinstance(b, float):
|
||||
return fallback_operator(float(a), b)
|
||||
elif isinstance(b, complex):
|
||||
@@ -369,7 +627,7 @@ class Fraction(numbers.Rational):
|
||||
def reverse(b, a):
|
||||
if isinstance(a, numbers.Rational):
|
||||
# Includes ints.
|
||||
return monomorphic_operator(a, b)
|
||||
return monomorphic_operator(Fraction(a), b)
|
||||
elif isinstance(a, numbers.Real):
|
||||
return fallback_operator(float(a), float(b))
|
||||
elif isinstance(a, numbers.Complex):
|
||||
@@ -451,40 +709,40 @@ class Fraction(numbers.Rational):
|
||||
|
||||
def _add(a, b):
|
||||
"""a + b"""
|
||||
na, da = a.numerator, a.denominator
|
||||
nb, db = b.numerator, b.denominator
|
||||
na, da = a._numerator, a._denominator
|
||||
nb, db = b._numerator, b._denominator
|
||||
g = math.gcd(da, db)
|
||||
if g == 1:
|
||||
return Fraction(na * db + da * nb, da * db, _normalize=False)
|
||||
return Fraction._from_coprime_ints(na * db + da * nb, da * db)
|
||||
s = da // g
|
||||
t = na * (db // g) + nb * s
|
||||
g2 = math.gcd(t, g)
|
||||
if g2 == 1:
|
||||
return Fraction(t, s * db, _normalize=False)
|
||||
return Fraction(t // g2, s * (db // g2), _normalize=False)
|
||||
return Fraction._from_coprime_ints(t, s * db)
|
||||
return Fraction._from_coprime_ints(t // g2, s * (db // g2))
|
||||
|
||||
__add__, __radd__ = _operator_fallbacks(_add, operator.add)
|
||||
|
||||
def _sub(a, b):
|
||||
"""a - b"""
|
||||
na, da = a.numerator, a.denominator
|
||||
nb, db = b.numerator, b.denominator
|
||||
na, da = a._numerator, a._denominator
|
||||
nb, db = b._numerator, b._denominator
|
||||
g = math.gcd(da, db)
|
||||
if g == 1:
|
||||
return Fraction(na * db - da * nb, da * db, _normalize=False)
|
||||
return Fraction._from_coprime_ints(na * db - da * nb, da * db)
|
||||
s = da // g
|
||||
t = na * (db // g) - nb * s
|
||||
g2 = math.gcd(t, g)
|
||||
if g2 == 1:
|
||||
return Fraction(t, s * db, _normalize=False)
|
||||
return Fraction(t // g2, s * (db // g2), _normalize=False)
|
||||
return Fraction._from_coprime_ints(t, s * db)
|
||||
return Fraction._from_coprime_ints(t // g2, s * (db // g2))
|
||||
|
||||
__sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub)
|
||||
|
||||
def _mul(a, b):
|
||||
"""a * b"""
|
||||
na, da = a.numerator, a.denominator
|
||||
nb, db = b.numerator, b.denominator
|
||||
na, da = a._numerator, a._denominator
|
||||
nb, db = b._numerator, b._denominator
|
||||
g1 = math.gcd(na, db)
|
||||
if g1 > 1:
|
||||
na //= g1
|
||||
@@ -493,15 +751,17 @@ class Fraction(numbers.Rational):
|
||||
if g2 > 1:
|
||||
nb //= g2
|
||||
da //= g2
|
||||
return Fraction(na * nb, db * da, _normalize=False)
|
||||
return Fraction._from_coprime_ints(na * nb, db * da)
|
||||
|
||||
__mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul)
|
||||
|
||||
def _div(a, b):
|
||||
"""a / b"""
|
||||
# Same as _mul(), with inversed b.
|
||||
na, da = a.numerator, a.denominator
|
||||
nb, db = b.numerator, b.denominator
|
||||
nb, db = b._numerator, b._denominator
|
||||
if nb == 0:
|
||||
raise ZeroDivisionError('Fraction(%s, 0)' % db)
|
||||
na, da = a._numerator, a._denominator
|
||||
g1 = math.gcd(na, nb)
|
||||
if g1 > 1:
|
||||
na //= g1
|
||||
@@ -513,7 +773,7 @@ class Fraction(numbers.Rational):
|
||||
n, d = na * db, nb * da
|
||||
if d < 0:
|
||||
n, d = -n, -d
|
||||
return Fraction(n, d, _normalize=False)
|
||||
return Fraction._from_coprime_ints(n, d)
|
||||
|
||||
__truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv)
|
||||
|
||||
@@ -550,17 +810,17 @@ class Fraction(numbers.Rational):
|
||||
if b.denominator == 1:
|
||||
power = b.numerator
|
||||
if power >= 0:
|
||||
return Fraction(a._numerator ** power,
|
||||
a._denominator ** power,
|
||||
_normalize=False)
|
||||
elif a._numerator >= 0:
|
||||
return Fraction(a._denominator ** -power,
|
||||
a._numerator ** -power,
|
||||
_normalize=False)
|
||||
return Fraction._from_coprime_ints(a._numerator ** power,
|
||||
a._denominator ** power)
|
||||
elif a._numerator > 0:
|
||||
return Fraction._from_coprime_ints(a._denominator ** -power,
|
||||
a._numerator ** -power)
|
||||
elif a._numerator == 0:
|
||||
raise ZeroDivisionError('Fraction(%s, 0)' %
|
||||
a._denominator ** -power)
|
||||
else:
|
||||
return Fraction((-a._denominator) ** -power,
|
||||
(-a._numerator) ** -power,
|
||||
_normalize=False)
|
||||
return Fraction._from_coprime_ints((-a._denominator) ** -power,
|
||||
(-a._numerator) ** -power)
|
||||
else:
|
||||
# A fractional power will generally produce an
|
||||
# irrational number.
|
||||
@@ -584,15 +844,15 @@ class Fraction(numbers.Rational):
|
||||
|
||||
def __pos__(a):
|
||||
"""+a: Coerces a subclass instance to Fraction"""
|
||||
return Fraction(a._numerator, a._denominator, _normalize=False)
|
||||
return Fraction._from_coprime_ints(a._numerator, a._denominator)
|
||||
|
||||
def __neg__(a):
|
||||
"""-a"""
|
||||
return Fraction(-a._numerator, a._denominator, _normalize=False)
|
||||
return Fraction._from_coprime_ints(-a._numerator, a._denominator)
|
||||
|
||||
def __abs__(a):
|
||||
"""abs(a)"""
|
||||
return Fraction(abs(a._numerator), a._denominator, _normalize=False)
|
||||
return Fraction._from_coprime_ints(abs(a._numerator), a._denominator)
|
||||
|
||||
def __int__(a, _index=operator.index):
|
||||
"""int(a)"""
|
||||
@@ -610,12 +870,12 @@ class Fraction(numbers.Rational):
|
||||
|
||||
def __floor__(a):
|
||||
"""math.floor(a)"""
|
||||
return a.numerator // a.denominator
|
||||
return a._numerator // a._denominator
|
||||
|
||||
def __ceil__(a):
|
||||
"""math.ceil(a)"""
|
||||
# The negations cleverly convince floordiv to return the ceiling.
|
||||
return -(-a.numerator // a.denominator)
|
||||
return -(-a._numerator // a._denominator)
|
||||
|
||||
def __round__(self, ndigits=None):
|
||||
"""round(self, ndigits)
|
||||
@@ -623,10 +883,11 @@ class Fraction(numbers.Rational):
|
||||
Rounds half toward even.
|
||||
"""
|
||||
if ndigits is None:
|
||||
floor, remainder = divmod(self.numerator, self.denominator)
|
||||
if remainder * 2 < self.denominator:
|
||||
d = self._denominator
|
||||
floor, remainder = divmod(self._numerator, d)
|
||||
if remainder * 2 < d:
|
||||
return floor
|
||||
elif remainder * 2 > self.denominator:
|
||||
elif remainder * 2 > d:
|
||||
return floor + 1
|
||||
# Deal with the half case:
|
||||
elif floor % 2 == 0:
|
||||
@@ -644,36 +905,7 @@ class Fraction(numbers.Rational):
|
||||
|
||||
def __hash__(self):
|
||||
"""hash(self)"""
|
||||
|
||||
# To make sure that the hash of a Fraction agrees with the hash
|
||||
# of a numerically equal integer, float or Decimal instance, we
|
||||
# follow the rules for numeric hashes outlined in the
|
||||
# documentation. (See library docs, 'Built-in Types').
|
||||
|
||||
try:
|
||||
dinv = pow(self._denominator, -1, _PyHASH_MODULUS)
|
||||
except ValueError:
|
||||
# ValueError means there is no modular inverse.
|
||||
hash_ = _PyHASH_INF
|
||||
else:
|
||||
# The general algorithm now specifies that the absolute value of
|
||||
# the hash is
|
||||
# (|N| * dinv) % P
|
||||
# where N is self._numerator and P is _PyHASH_MODULUS. That's
|
||||
# optimized here in two ways: first, for a non-negative int i,
|
||||
# hash(i) == i % P, but the int hash implementation doesn't need
|
||||
# to divide, and is faster than doing % P explicitly. So we do
|
||||
# hash(|N| * dinv)
|
||||
# instead. Second, N is unbounded, so its product with dinv may
|
||||
# be arbitrarily expensive to compute. The final answer is the
|
||||
# same if we use the bounded |N| % P instead, which can again
|
||||
# be done with an int hash() call. If 0 <= i < P, hash(i) == i,
|
||||
# so this nested hash() call wastes a bit of time making a
|
||||
# redundant copy when |N| < P, but can save an arbitrarily large
|
||||
# amount of computation for large |N|.
|
||||
hash_ = hash(hash(abs(self._numerator)) * dinv)
|
||||
result = hash_ if self._numerator >= 0 else -hash_
|
||||
return -2 if result == -1 else result
|
||||
return _hash_algorithm(self._numerator, self._denominator)
|
||||
|
||||
def __eq__(a, b):
|
||||
"""a == b"""
|
||||
|
||||
Vendored
+738
-4
@@ -1,5 +1,6 @@
|
||||
"""Tests for Lib/fractions.py."""
|
||||
|
||||
import cmath
|
||||
from decimal import Decimal
|
||||
# from test.support import requires_IEEE_754
|
||||
import math
|
||||
@@ -7,13 +8,18 @@ import numbers
|
||||
import operator
|
||||
import fractions
|
||||
import functools
|
||||
import os
|
||||
import sys
|
||||
import typing
|
||||
import unittest
|
||||
from copy import copy, deepcopy
|
||||
import pickle
|
||||
from pickle import dumps, loads
|
||||
F = fractions.Fraction
|
||||
|
||||
#locate file with float format test values
|
||||
test_dir = os.path.dirname(__file__) or os.curdir
|
||||
format_testfile = os.path.join(test_dir, 'formatfloat_testcases.txt')
|
||||
|
||||
class DummyFloat(object):
|
||||
"""Dummy float class for testing comparisons with Fractions"""
|
||||
@@ -86,6 +92,197 @@ 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 SymbolicReal(Symbolic):
|
||||
pass
|
||||
numbers.Real.register(SymbolicReal)
|
||||
|
||||
class SymbolicComplex(Symbolic):
|
||||
pass
|
||||
numbers.Complex.register(SymbolicComplex)
|
||||
|
||||
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 RectComplex(Rect, complex):
|
||||
pass
|
||||
|
||||
class FractionTest(unittest.TestCase):
|
||||
|
||||
@@ -161,6 +358,7 @@ class FractionTest(unittest.TestCase):
|
||||
def testFromString(self):
|
||||
self.assertEqual((5, 1), _components(F("5")))
|
||||
self.assertEqual((3, 2), _components(F("3/2")))
|
||||
self.assertEqual((3, 2), _components(F("3 / 2")))
|
||||
self.assertEqual((3, 2), _components(F(" \n +3/2")))
|
||||
self.assertEqual((-3, 2), _components(F("-3/2 ")))
|
||||
self.assertEqual((13, 2), _components(F(" 013/02 \n ")))
|
||||
@@ -189,9 +387,6 @@ class FractionTest(unittest.TestCase):
|
||||
self.assertRaisesMessage(
|
||||
ValueError, "Invalid literal for Fraction: '/2'",
|
||||
F, "/2")
|
||||
self.assertRaisesMessage(
|
||||
ValueError, "Invalid literal for Fraction: '3 /2'",
|
||||
F, "3 /2")
|
||||
self.assertRaisesMessage(
|
||||
# Denominators don't need a sign.
|
||||
ValueError, "Invalid literal for Fraction: '3/+2'",
|
||||
@@ -258,6 +453,30 @@ class FractionTest(unittest.TestCase):
|
||||
self.assertRaisesMessage(
|
||||
ValueError, "Invalid literal for Fraction: '1.1e+1__1'",
|
||||
F, "1.1e+1__1")
|
||||
self.assertRaisesMessage(
|
||||
ValueError, "Invalid literal for Fraction: '123.dd'",
|
||||
F, "123.dd")
|
||||
self.assertRaisesMessage(
|
||||
ValueError, "Invalid literal for Fraction: '123.5_dd'",
|
||||
F, "123.5_dd")
|
||||
self.assertRaisesMessage(
|
||||
ValueError, "Invalid literal for Fraction: 'dd.5'",
|
||||
F, "dd.5")
|
||||
self.assertRaisesMessage(
|
||||
ValueError, "Invalid literal for Fraction: '7_dd'",
|
||||
F, "7_dd")
|
||||
self.assertRaisesMessage(
|
||||
ValueError, "Invalid literal for Fraction: '1/dd'",
|
||||
F, "1/dd")
|
||||
self.assertRaisesMessage(
|
||||
ValueError, "Invalid literal for Fraction: '1/123_dd'",
|
||||
F, "1/123_dd")
|
||||
self.assertRaisesMessage(
|
||||
ValueError, "Invalid literal for Fraction: '789edd'",
|
||||
F, "789edd")
|
||||
self.assertRaisesMessage(
|
||||
ValueError, "Invalid literal for Fraction: '789e2_dd'",
|
||||
F, "789e2_dd")
|
||||
# Test catastrophic backtracking.
|
||||
val = "9"*50 + "_"
|
||||
self.assertRaisesMessage(
|
||||
@@ -341,6 +560,19 @@ class FractionTest(unittest.TestCase):
|
||||
ValueError, "cannot convert NaN to integer ratio",
|
||||
F.from_decimal, Decimal("snan"))
|
||||
|
||||
def test_is_integer(self):
|
||||
self.assertTrue(F(1, 1).is_integer())
|
||||
self.assertTrue(F(-1, 1).is_integer())
|
||||
self.assertTrue(F(1, -1).is_integer())
|
||||
self.assertTrue(F(2, 2).is_integer())
|
||||
self.assertTrue(F(-2, 2).is_integer())
|
||||
self.assertTrue(F(2, -2).is_integer())
|
||||
|
||||
self.assertFalse(F(1, 2).is_integer())
|
||||
self.assertFalse(F(-1, 2).is_integer())
|
||||
self.assertFalse(F(1, -2).is_integer())
|
||||
self.assertFalse(F(-1, -2).is_integer())
|
||||
|
||||
def test_as_integer_ratio(self):
|
||||
self.assertEqual(F(4, 6).as_integer_ratio(), (2, 3))
|
||||
self.assertEqual(F(-4, 6).as_integer_ratio(), (-2, 3))
|
||||
@@ -476,6 +708,7 @@ class FractionTest(unittest.TestCase):
|
||||
self.assertEqual(F(5, 6), F(2, 3) * F(5, 4))
|
||||
self.assertEqual(F(1, 4), F(1, 10) / F(2, 5))
|
||||
self.assertEqual(F(-15, 8), F(3, 4) / F(-2, 5))
|
||||
self.assertRaises(ZeroDivisionError, operator.truediv, F(1), F(0))
|
||||
self.assertTypedEquals(2, F(9, 10) // F(2, 5))
|
||||
self.assertTypedEquals(10**23, F(10**23, 1) // F(1))
|
||||
self.assertEqual(F(5, 6), F(7, 3) % F(3, 2))
|
||||
@@ -552,6 +785,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))
|
||||
@@ -559,6 +793,29 @@ 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.assertEqual(F(3, 2) * SymbolicReal('X'), SymbolicReal('3/2 * X'))
|
||||
self.assertRaises(TypeError, operator.mul, SymbolicReal('X'), F(3, 2))
|
||||
|
||||
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.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'))
|
||||
self.assertRaises(TypeError, operator.mul, SymbolicComplex('X'), F(3, 2))
|
||||
|
||||
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))
|
||||
@@ -566,6 +823,28 @@ 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.assertEqual(F(3, 2) / SymbolicReal('X'), SymbolicReal('3/2 / X'))
|
||||
self.assertRaises(TypeError, operator.truediv, SymbolicReal('X'), F(3, 2))
|
||||
|
||||
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) / SymbolicComplex('X'), SymbolicComplex('3/2 / X'))
|
||||
self.assertRaises(TypeError, operator.truediv, SymbolicComplex('X'), F(3, 2))
|
||||
|
||||
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))
|
||||
@@ -590,6 +869,26 @@ 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.assertEqual(F(3, 2) % SymbolicReal('X'), SymbolicReal('3/2 % X'))
|
||||
self.assertRaises(TypeError, operator.mod, SymbolicReal('X'), F(3, 2))
|
||||
|
||||
self.assertRaises(TypeError, operator.mod, F(3, 2), Polar(4, 2))
|
||||
self.assertRaises(TypeError, operator.mod, F(3, 2), RectComplex(4, 3))
|
||||
self.assertRaises(TypeError, operator.mod, Rect(4, 3), F(2, 3))
|
||||
self.assertEqual(F(3, 2) % SymbolicComplex('X'), SymbolicComplex('3/2 % X'))
|
||||
self.assertRaises(TypeError, operator.mod, SymbolicComplex('X'), F(3, 2))
|
||||
|
||||
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)
|
||||
@@ -606,6 +905,40 @@ 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.assertEqual(F(3, 2) ** SymbolicReal('X'), SymbolicReal('1.5 ** X'))
|
||||
self.assertEqual(SymbolicReal('X') ** F(3, 2), SymbolicReal('X ** 1.5'))
|
||||
|
||||
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(F(3, 2) ** RectComplex(2, 0), Polar(2.25, 0.0))
|
||||
self.assertTypedEquals(F(1, 1) ** RectComplex(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.assertEqual(F(3, 2) ** SymbolicComplex('X'), SymbolicComplex('1.5 ** X'))
|
||||
self.assertEqual(SymbolicComplex('X') ** F(3, 2), SymbolicComplex('X ** 1.5'))
|
||||
|
||||
self.assertEqual(F(3, 2) ** Symbolic('X'), Symbolic('1.5 ** X'))
|
||||
self.assertEqual(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,
|
||||
@@ -795,7 +1128,8 @@ class FractionTest(unittest.TestCase):
|
||||
def test_copy_deepcopy_pickle(self):
|
||||
r = F(13, 7)
|
||||
dr = DummyFraction(13, 7)
|
||||
self.assertEqual(r, loads(dumps(r)))
|
||||
for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
|
||||
self.assertEqual(r, loads(dumps(r, proto)))
|
||||
self.assertEqual(id(r), id(copy(r)))
|
||||
self.assertEqual(id(r), id(deepcopy(r)))
|
||||
self.assertNotEqual(id(dr), id(copy(dr)))
|
||||
@@ -832,6 +1166,406 @@ class FractionTest(unittest.TestCase):
|
||||
self.assertEqual(type(f.numerator), myint)
|
||||
self.assertEqual(type(f.denominator), myint)
|
||||
|
||||
def test_format_no_presentation_type(self):
|
||||
# Triples (fraction, specification, expected_result)
|
||||
testcases = [
|
||||
(F(1, 3), '', '1/3'),
|
||||
(F(-1, 3), '', '-1/3'),
|
||||
(F(3), '', '3'),
|
||||
(F(-3), '', '-3'),
|
||||
]
|
||||
for fraction, spec, expected in testcases:
|
||||
with self.subTest(fraction=fraction, spec=spec):
|
||||
self.assertEqual(format(fraction, spec), expected)
|
||||
|
||||
def test_format_e_presentation_type(self):
|
||||
# Triples (fraction, specification, expected_result)
|
||||
testcases = [
|
||||
(F(2, 3), '.6e', '6.666667e-01'),
|
||||
(F(3, 2), '.6e', '1.500000e+00'),
|
||||
(F(2, 13), '.6e', '1.538462e-01'),
|
||||
(F(2, 23), '.6e', '8.695652e-02'),
|
||||
(F(2, 33), '.6e', '6.060606e-02'),
|
||||
(F(13, 2), '.6e', '6.500000e+00'),
|
||||
(F(20, 2), '.6e', '1.000000e+01'),
|
||||
(F(23, 2), '.6e', '1.150000e+01'),
|
||||
(F(33, 2), '.6e', '1.650000e+01'),
|
||||
(F(2, 3), '.6e', '6.666667e-01'),
|
||||
(F(3, 2), '.6e', '1.500000e+00'),
|
||||
# Zero
|
||||
(F(0), '.3e', '0.000e+00'),
|
||||
# Powers of 10, to exercise the log10 boundary logic
|
||||
(F(1, 1000), '.3e', '1.000e-03'),
|
||||
(F(1, 100), '.3e', '1.000e-02'),
|
||||
(F(1, 10), '.3e', '1.000e-01'),
|
||||
(F(1, 1), '.3e', '1.000e+00'),
|
||||
(F(10), '.3e', '1.000e+01'),
|
||||
(F(100), '.3e', '1.000e+02'),
|
||||
(F(1000), '.3e', '1.000e+03'),
|
||||
# Boundary where we round up to the next power of 10
|
||||
(F('99.999994999999'), '.6e', '9.999999e+01'),
|
||||
(F('99.999995'), '.6e', '1.000000e+02'),
|
||||
(F('99.999995000001'), '.6e', '1.000000e+02'),
|
||||
# Negatives
|
||||
(F(-2, 3), '.6e', '-6.666667e-01'),
|
||||
(F(-3, 2), '.6e', '-1.500000e+00'),
|
||||
(F(-100), '.6e', '-1.000000e+02'),
|
||||
# Large and small
|
||||
(F('1e1000'), '.3e', '1.000e+1000'),
|
||||
(F('1e-1000'), '.3e', '1.000e-1000'),
|
||||
# Using 'E' instead of 'e' should give us a capital 'E'
|
||||
(F(2, 3), '.6E', '6.666667E-01'),
|
||||
# Tiny precision
|
||||
(F(2, 3), '.1e', '6.7e-01'),
|
||||
(F('0.995'), '.0e', '1e+00'),
|
||||
# Default precision is 6
|
||||
(F(22, 7), 'e', '3.142857e+00'),
|
||||
# Alternate form forces a decimal point
|
||||
(F('0.995'), '#.0e', '1.e+00'),
|
||||
# Check that padding takes the exponent into account.
|
||||
(F(22, 7), '11.6e', '3.142857e+00'),
|
||||
(F(22, 7), '12.6e', '3.142857e+00'),
|
||||
(F(22, 7), '13.6e', ' 3.142857e+00'),
|
||||
# Thousands separators
|
||||
(F('1234567.123456'), ',.5e', '1.23457e+06'),
|
||||
(F('123.123456'), '012_.2e', '0_001.23e+02'),
|
||||
# z flag is legal, but never makes a difference to the output
|
||||
(F(-1, 7**100), 'z.6e', '-3.091690e-85'),
|
||||
]
|
||||
for fraction, spec, expected in testcases:
|
||||
with self.subTest(fraction=fraction, spec=spec):
|
||||
self.assertEqual(format(fraction, spec), expected)
|
||||
|
||||
def test_format_f_presentation_type(self):
|
||||
# Triples (fraction, specification, expected_result)
|
||||
testcases = [
|
||||
# Simple .f formatting
|
||||
(F(0, 1), '.2f', '0.00'),
|
||||
(F(1, 3), '.2f', '0.33'),
|
||||
(F(2, 3), '.2f', '0.67'),
|
||||
(F(4, 3), '.2f', '1.33'),
|
||||
(F(1, 8), '.2f', '0.12'),
|
||||
(F(3, 8), '.2f', '0.38'),
|
||||
(F(1, 13), '.2f', '0.08'),
|
||||
(F(1, 199), '.2f', '0.01'),
|
||||
(F(1, 200), '.2f', '0.00'),
|
||||
(F(22, 7), '.5f', '3.14286'),
|
||||
(F('399024789'), '.2f', '399024789.00'),
|
||||
# Large precision (more than float can provide)
|
||||
(F(104348, 33215), '.50f',
|
||||
'3.14159265392142104470871594159265392142104470871594'),
|
||||
# Precision defaults to 6 if not given
|
||||
(F(22, 7), 'f', '3.142857'),
|
||||
(F(0), 'f', '0.000000'),
|
||||
(F(-22, 7), 'f', '-3.142857'),
|
||||
# Round-ties-to-even checks
|
||||
(F('1.225'), '.2f', '1.22'),
|
||||
(F('1.2250000001'), '.2f', '1.23'),
|
||||
(F('1.2349999999'), '.2f', '1.23'),
|
||||
(F('1.235'), '.2f', '1.24'),
|
||||
(F('1.245'), '.2f', '1.24'),
|
||||
(F('1.2450000001'), '.2f', '1.25'),
|
||||
(F('1.2549999999'), '.2f', '1.25'),
|
||||
(F('1.255'), '.2f', '1.26'),
|
||||
(F('-1.225'), '.2f', '-1.22'),
|
||||
(F('-1.2250000001'), '.2f', '-1.23'),
|
||||
(F('-1.2349999999'), '.2f', '-1.23'),
|
||||
(F('-1.235'), '.2f', '-1.24'),
|
||||
(F('-1.245'), '.2f', '-1.24'),
|
||||
(F('-1.2450000001'), '.2f', '-1.25'),
|
||||
(F('-1.2549999999'), '.2f', '-1.25'),
|
||||
(F('-1.255'), '.2f', '-1.26'),
|
||||
# Negatives and sign handling
|
||||
(F(2, 3), '.2f', '0.67'),
|
||||
(F(2, 3), '-.2f', '0.67'),
|
||||
(F(2, 3), '+.2f', '+0.67'),
|
||||
(F(2, 3), ' .2f', ' 0.67'),
|
||||
(F(-2, 3), '.2f', '-0.67'),
|
||||
(F(-2, 3), '-.2f', '-0.67'),
|
||||
(F(-2, 3), '+.2f', '-0.67'),
|
||||
(F(-2, 3), ' .2f', '-0.67'),
|
||||
# Formatting to zero places
|
||||
(F(1, 2), '.0f', '0'),
|
||||
(F(-1, 2), '.0f', '-0'),
|
||||
(F(22, 7), '.0f', '3'),
|
||||
(F(-22, 7), '.0f', '-3'),
|
||||
# Formatting to zero places, alternate form
|
||||
(F(1, 2), '#.0f', '0.'),
|
||||
(F(-1, 2), '#.0f', '-0.'),
|
||||
(F(22, 7), '#.0f', '3.'),
|
||||
(F(-22, 7), '#.0f', '-3.'),
|
||||
# z flag for suppressing negative zeros
|
||||
(F('-0.001'), 'z.2f', '0.00'),
|
||||
(F('-0.001'), '-z.2f', '0.00'),
|
||||
(F('-0.001'), '+z.2f', '+0.00'),
|
||||
(F('-0.001'), ' z.2f', ' 0.00'),
|
||||
(F('0.001'), 'z.2f', '0.00'),
|
||||
(F('0.001'), '-z.2f', '0.00'),
|
||||
(F('0.001'), '+z.2f', '+0.00'),
|
||||
(F('0.001'), ' z.2f', ' 0.00'),
|
||||
# Specifying a minimum width
|
||||
(F(2, 3), '6.2f', ' 0.67'),
|
||||
(F(12345), '6.2f', '12345.00'),
|
||||
(F(12345), '12f', '12345.000000'),
|
||||
# Fill and alignment
|
||||
(F(2, 3), '>6.2f', ' 0.67'),
|
||||
(F(2, 3), '<6.2f', '0.67 '),
|
||||
(F(2, 3), '^3.2f', '0.67'),
|
||||
(F(2, 3), '^4.2f', '0.67'),
|
||||
(F(2, 3), '^5.2f', '0.67 '),
|
||||
(F(2, 3), '^6.2f', ' 0.67 '),
|
||||
(F(2, 3), '^7.2f', ' 0.67 '),
|
||||
(F(2, 3), '^8.2f', ' 0.67 '),
|
||||
# '=' alignment
|
||||
(F(-2, 3), '=+8.2f', '- 0.67'),
|
||||
(F(2, 3), '=+8.2f', '+ 0.67'),
|
||||
# Fill character
|
||||
(F(-2, 3), 'X>3.2f', '-0.67'),
|
||||
(F(-2, 3), 'X>7.2f', 'XX-0.67'),
|
||||
(F(-2, 3), 'X<7.2f', '-0.67XX'),
|
||||
(F(-2, 3), 'X^7.2f', 'X-0.67X'),
|
||||
(F(-2, 3), 'X=7.2f', '-XX0.67'),
|
||||
(F(-2, 3), ' >7.2f', ' -0.67'),
|
||||
# Corner cases: weird fill characters
|
||||
(F(-2, 3), '\x00>7.2f', '\x00\x00-0.67'),
|
||||
(F(-2, 3), '\n>7.2f', '\n\n-0.67'),
|
||||
(F(-2, 3), '\t>7.2f', '\t\t-0.67'),
|
||||
(F(-2, 3), '>>7.2f', '>>-0.67'),
|
||||
(F(-2, 3), '<>7.2f', '<<-0.67'),
|
||||
(F(-2, 3), '→>7.2f', '→→-0.67'),
|
||||
# Zero-padding
|
||||
(F(-2, 3), '07.2f', '-000.67'),
|
||||
(F(-2, 3), '-07.2f', '-000.67'),
|
||||
(F(2, 3), '+07.2f', '+000.67'),
|
||||
(F(2, 3), ' 07.2f', ' 000.67'),
|
||||
# An isolated zero is a minimum width, not a zero-pad flag.
|
||||
# So unlike zero-padding, it's legal in combination with alignment.
|
||||
(F(2, 3), '0.2f', '0.67'),
|
||||
(F(2, 3), '>0.2f', '0.67'),
|
||||
(F(2, 3), '<0.2f', '0.67'),
|
||||
(F(2, 3), '^0.2f', '0.67'),
|
||||
(F(2, 3), '=0.2f', '0.67'),
|
||||
# Corner case: zero-padding _and_ a zero minimum width.
|
||||
(F(2, 3), '00.2f', '0.67'),
|
||||
# Thousands separator (only affects portion before the point)
|
||||
(F(2, 3), ',.2f', '0.67'),
|
||||
(F(2, 3), ',.7f', '0.6666667'),
|
||||
(F('123456.789'), ',.2f', '123,456.79'),
|
||||
(F('1234567'), ',.2f', '1,234,567.00'),
|
||||
(F('12345678'), ',.2f', '12,345,678.00'),
|
||||
(F('12345678'), ',f', '12,345,678.000000'),
|
||||
# Underscore as thousands separator
|
||||
(F(2, 3), '_.2f', '0.67'),
|
||||
(F(2, 3), '_.7f', '0.6666667'),
|
||||
(F('123456.789'), '_.2f', '123_456.79'),
|
||||
(F('1234567'), '_.2f', '1_234_567.00'),
|
||||
(F('12345678'), '_.2f', '12_345_678.00'),
|
||||
# Thousands and zero-padding
|
||||
(F('1234.5678'), '07,.2f', '1,234.57'),
|
||||
(F('1234.5678'), '08,.2f', '1,234.57'),
|
||||
(F('1234.5678'), '09,.2f', '01,234.57'),
|
||||
(F('1234.5678'), '010,.2f', '001,234.57'),
|
||||
(F('1234.5678'), '011,.2f', '0,001,234.57'),
|
||||
(F('1234.5678'), '012,.2f', '0,001,234.57'),
|
||||
(F('1234.5678'), '013,.2f', '00,001,234.57'),
|
||||
(F('1234.5678'), '014,.2f', '000,001,234.57'),
|
||||
(F('1234.5678'), '015,.2f', '0,000,001,234.57'),
|
||||
(F('1234.5678'), '016,.2f', '0,000,001,234.57'),
|
||||
(F('-1234.5678'), '07,.2f', '-1,234.57'),
|
||||
(F('-1234.5678'), '08,.2f', '-1,234.57'),
|
||||
(F('-1234.5678'), '09,.2f', '-1,234.57'),
|
||||
(F('-1234.5678'), '010,.2f', '-01,234.57'),
|
||||
(F('-1234.5678'), '011,.2f', '-001,234.57'),
|
||||
(F('-1234.5678'), '012,.2f', '-0,001,234.57'),
|
||||
(F('-1234.5678'), '013,.2f', '-0,001,234.57'),
|
||||
(F('-1234.5678'), '014,.2f', '-00,001,234.57'),
|
||||
(F('-1234.5678'), '015,.2f', '-000,001,234.57'),
|
||||
(F('-1234.5678'), '016,.2f', '-0,000,001,234.57'),
|
||||
# Corner case: no decimal point
|
||||
(F('-1234.5678'), '06,.0f', '-1,235'),
|
||||
(F('-1234.5678'), '07,.0f', '-01,235'),
|
||||
(F('-1234.5678'), '08,.0f', '-001,235'),
|
||||
(F('-1234.5678'), '09,.0f', '-0,001,235'),
|
||||
# Corner-case - zero-padding specified through fill and align
|
||||
# instead of the zero-pad character - in this case, treat '0' as a
|
||||
# regular fill character and don't attempt to insert commas into
|
||||
# the filled portion. This differs from the int and float
|
||||
# behaviour.
|
||||
(F('1234.5678'), '0=12,.2f', '00001,234.57'),
|
||||
# Corner case where it's not clear whether the '0' indicates zero
|
||||
# padding or gives the minimum width, but there's still an obvious
|
||||
# answer to give. We want this to work in case the minimum width
|
||||
# is being inserted programmatically: spec = f'{width}.2f'.
|
||||
(F('12.34'), '0.2f', '12.34'),
|
||||
(F('12.34'), 'X>0.2f', '12.34'),
|
||||
# 'F' should work identically to 'f'
|
||||
(F(22, 7), '.5F', '3.14286'),
|
||||
# %-specifier
|
||||
(F(22, 7), '.2%', '314.29%'),
|
||||
(F(1, 7), '.2%', '14.29%'),
|
||||
(F(1, 70), '.2%', '1.43%'),
|
||||
(F(1, 700), '.2%', '0.14%'),
|
||||
(F(1, 7000), '.2%', '0.01%'),
|
||||
(F(1, 70000), '.2%', '0.00%'),
|
||||
(F(1, 7), '.0%', '14%'),
|
||||
(F(1, 7), '#.0%', '14.%'),
|
||||
(F(100, 7), ',.2%', '1,428.57%'),
|
||||
(F(22, 7), '7.2%', '314.29%'),
|
||||
(F(22, 7), '8.2%', ' 314.29%'),
|
||||
(F(22, 7), '08.2%', '0314.29%'),
|
||||
# Test cases from #67790 and discuss.python.org Ideas thread.
|
||||
(F(1, 3), '.2f', '0.33'),
|
||||
(F(1, 8), '.2f', '0.12'),
|
||||
(F(3, 8), '.2f', '0.38'),
|
||||
(F(2545, 1000), '.2f', '2.54'),
|
||||
(F(2549, 1000), '.2f', '2.55'),
|
||||
(F(2635, 1000), '.2f', '2.64'),
|
||||
(F(1, 100), '.1f', '0.0'),
|
||||
(F(49, 1000), '.1f', '0.0'),
|
||||
(F(51, 1000), '.1f', '0.1'),
|
||||
(F(149, 1000), '.1f', '0.1'),
|
||||
(F(151, 1000), '.1f', '0.2'),
|
||||
]
|
||||
for fraction, spec, expected in testcases:
|
||||
with self.subTest(fraction=fraction, spec=spec):
|
||||
self.assertEqual(format(fraction, spec), expected)
|
||||
|
||||
def test_format_g_presentation_type(self):
|
||||
# Triples (fraction, specification, expected_result)
|
||||
testcases = [
|
||||
(F('0.000012345678'), '.6g', '1.23457e-05'),
|
||||
(F('0.00012345678'), '.6g', '0.000123457'),
|
||||
(F('0.0012345678'), '.6g', '0.00123457'),
|
||||
(F('0.012345678'), '.6g', '0.0123457'),
|
||||
(F('0.12345678'), '.6g', '0.123457'),
|
||||
(F('1.2345678'), '.6g', '1.23457'),
|
||||
(F('12.345678'), '.6g', '12.3457'),
|
||||
(F('123.45678'), '.6g', '123.457'),
|
||||
(F('1234.5678'), '.6g', '1234.57'),
|
||||
(F('12345.678'), '.6g', '12345.7'),
|
||||
(F('123456.78'), '.6g', '123457'),
|
||||
(F('1234567.8'), '.6g', '1.23457e+06'),
|
||||
# Rounding up cases
|
||||
(F('9.99999e+2'), '.4g', '1000'),
|
||||
(F('9.99999e-8'), '.4g', '1e-07'),
|
||||
(F('9.99999e+8'), '.4g', '1e+09'),
|
||||
# Check round-ties-to-even behaviour
|
||||
(F('-0.115'), '.2g', '-0.12'),
|
||||
(F('-0.125'), '.2g', '-0.12'),
|
||||
(F('-0.135'), '.2g', '-0.14'),
|
||||
(F('-0.145'), '.2g', '-0.14'),
|
||||
(F('0.115'), '.2g', '0.12'),
|
||||
(F('0.125'), '.2g', '0.12'),
|
||||
(F('0.135'), '.2g', '0.14'),
|
||||
(F('0.145'), '.2g', '0.14'),
|
||||
# Trailing zeros and decimal point suppressed by default ...
|
||||
(F(0), '.6g', '0'),
|
||||
(F('123.400'), '.6g', '123.4'),
|
||||
(F('123.000'), '.6g', '123'),
|
||||
(F('120.000'), '.6g', '120'),
|
||||
(F('12000000'), '.6g', '1.2e+07'),
|
||||
# ... but not when alternate form is in effect
|
||||
(F(0), '#.6g', '0.00000'),
|
||||
(F('123.400'), '#.6g', '123.400'),
|
||||
(F('123.000'), '#.6g', '123.000'),
|
||||
(F('120.000'), '#.6g', '120.000'),
|
||||
(F('12000000'), '#.6g', '1.20000e+07'),
|
||||
# 'G' format (uses 'E' instead of 'e' for the exponent indicator)
|
||||
(F('123.45678'), '.6G', '123.457'),
|
||||
(F('1234567.8'), '.6G', '1.23457E+06'),
|
||||
# Default precision is 6 significant figures
|
||||
(F('3.1415926535'), 'g', '3.14159'),
|
||||
# Precision 0 is treated the same as precision 1.
|
||||
(F('0.000031415'), '.0g', '3e-05'),
|
||||
(F('0.00031415'), '.0g', '0.0003'),
|
||||
(F('0.31415'), '.0g', '0.3'),
|
||||
(F('3.1415'), '.0g', '3'),
|
||||
(F('3.1415'), '#.0g', '3.'),
|
||||
(F('31.415'), '.0g', '3e+01'),
|
||||
(F('31.415'), '#.0g', '3.e+01'),
|
||||
(F('0.000031415'), '.1g', '3e-05'),
|
||||
(F('0.00031415'), '.1g', '0.0003'),
|
||||
(F('0.31415'), '.1g', '0.3'),
|
||||
(F('3.1415'), '.1g', '3'),
|
||||
(F('3.1415'), '#.1g', '3.'),
|
||||
(F('31.415'), '.1g', '3e+01'),
|
||||
# Thousands separator
|
||||
(F(2**64), '_.25g', '18_446_744_073_709_551_616'),
|
||||
# As with 'e' format, z flag is legal, but has no effect
|
||||
(F(-1, 7**100), 'zg', '-3.09169e-85'),
|
||||
]
|
||||
for fraction, spec, expected in testcases:
|
||||
with self.subTest(fraction=fraction, spec=spec):
|
||||
self.assertEqual(format(fraction, spec), expected)
|
||||
|
||||
def test_invalid_formats(self):
|
||||
fraction = F(2, 3)
|
||||
with self.assertRaises(TypeError):
|
||||
format(fraction, None)
|
||||
|
||||
invalid_specs = [
|
||||
'Q6f', # regression test
|
||||
# illegal to use fill or alignment when zero padding
|
||||
'X>010f',
|
||||
'X<010f',
|
||||
'X^010f',
|
||||
'X=010f',
|
||||
'0>010f',
|
||||
'0<010f',
|
||||
'0^010f',
|
||||
'0=010f',
|
||||
'>010f',
|
||||
'<010f',
|
||||
'^010f',
|
||||
'=010e',
|
||||
'=010f',
|
||||
'=010g',
|
||||
'=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',
|
||||
'.%',
|
||||
# Z instead of z for negative zero suppression
|
||||
'Z.2f'
|
||||
]
|
||||
for spec in invalid_specs:
|
||||
with self.subTest(spec=spec):
|
||||
with self.assertRaises(ValueError):
|
||||
format(fraction, spec)
|
||||
|
||||
# @requires_IEEE_754
|
||||
def test_float_format_testfile(self):
|
||||
with open(format_testfile, encoding="utf-8") as testfile:
|
||||
for line in testfile:
|
||||
if line.startswith('--'):
|
||||
continue
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
lhs, rhs = map(str.strip, line.split('->'))
|
||||
fmt, arg = lhs.split()
|
||||
if fmt == '%r':
|
||||
continue
|
||||
fmt2 = fmt[1:]
|
||||
with self.subTest(fmt=fmt, arg=arg):
|
||||
f = F(float(arg))
|
||||
self.assertEqual(format(f, fmt2), rhs)
|
||||
if f: # skip negative zero
|
||||
self.assertEqual(format(-f, fmt2), '-' + rhs)
|
||||
f = F(arg)
|
||||
self.assertEqual(float(format(f, fmt2)), float(rhs))
|
||||
self.assertEqual(float(format(-f, fmt2)), float('-' + rhs))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Vendored
+1
-1
@@ -145,7 +145,7 @@ class HashTest(unittest.TestCase):
|
||||
# The numbers ABC doesn't enforce that the "true" division
|
||||
# of integers produces a float. This tests that the
|
||||
# Rational.__float__() method has required type conversions.
|
||||
x = F(DummyIntegral(1), DummyIntegral(2), _normalize=False)
|
||||
x = F._from_coprime_ints(DummyIntegral(1), DummyIntegral(2))
|
||||
self.assertRaises(TypeError, lambda: x.numerator/x.denominator)
|
||||
self.assertEqual(float(x), 0.5)
|
||||
|
||||
|
||||
Reference in new issue
Block a user