Update fraction,test_numeric_tower from CPython 3.12.4

This commit is contained in:
CPython Developers authored and Jeong, YunWon committed 2024-07-29 19:53:44 +09:00
1 parent 694354e5e6
commit 09d74336fa
3 files changed
+1065 -99

No files matched your search

+326 -94
View File
@@ -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"""
+738 -4
View File
@@ -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()
+1 -1
View File
@@ -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)