Skip to content

Update test_math.py to cpython 3.10 #3448

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 20, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 93 additions & 27 deletions Lib/test/test_math.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Python test set -- math module
# XXXX Should not do tests around zero only

Expand All @@ -19,7 +18,6 @@
NAN = float('nan')
INF = float('inf')
NINF = float('-inf')

FLOAT_MAX = sys.float_info.max
FLOAT_MIN = sys.float_info.min

Expand All @@ -43,8 +41,10 @@ def to_ulps(x):
adjacent floats are converted to adjacent integers. Then
abs(ulps(x) - ulps(y)) gives the difference in ulps between two
floats.

The results from this function will only make sense on platforms
where native doubles are represented in IEEE 754 binary64 format.

Note: 0.0 and -0.0 are converted to 0 and -1, respectively.
"""
n = struct.unpack('<q', struct.pack('<d', x))[0]
Expand Down Expand Up @@ -81,6 +81,7 @@ def count_set_bits(n):
def partial_product(start, stop):
"""Product of integers in range(start, stop, 2), computed recursively.
start and stop should both be odd, with start <= stop.

"""
numfactors = (stop - start) >> 1
if not numfactors:
Expand All @@ -94,6 +95,7 @@ def partial_product(start, stop):
def py_factorial(n):
"""Factorial of nonnegative integer n, via "Binary Split Factorial Formula"
described at http://www.luschny.de/math/factorial/binarysplitfact.html

"""
inner = outer = 1
for i in reversed(range(n.bit_length())):
Expand All @@ -105,6 +107,7 @@ def ulp_abs_check(expected, got, ulp_tol, abs_tol):
"""Given finite floats `expected` and `got`, check that they're
approximately equal to within the given number of ulps or the
given absolute tolerance, whichever is bigger.

Returns None on success and an error message on failure.
"""
ulp_error = abs(to_ulps(expected) - to_ulps(got))
Expand All @@ -120,12 +123,14 @@ def ulp_abs_check(expected, got, ulp_tol, abs_tol):

def parse_mtestfile(fname):
"""Parse a file with test values

-- starts a comment
blank lines, or lines containing only a comment, are ignored
other lines are expected to have the form
id fn arg -> expected [flag]*

"""
with open(fname) as fp:
with open(fname, encoding="utf-8") as fp:
for line in fp:
# strip comments, and skip blank lines
if '--' in line:
Expand All @@ -144,10 +149,11 @@ def parse_mtestfile(fname):

def parse_testfile(fname):
"""Parse a file with test values

Empty lines or lines starting with -- are ignored
yields id, fn, arg_real, arg_imag, exp_real, exp_imag
"""
with open(fname) as fp:
with open(fname, encoding="utf-8") as fp:
for line in fp:
# skip comment lines and blank lines
if line.startswith('--') or not line.strip():
Expand All @@ -170,9 +176,11 @@ def result_check(expected, got, ulp_tol=5, abs_tol=0.0):
"""Compare arguments expected and got, as floats, if either
is a float, using a tolerance expressed in multiples of
ulp(expected) or absolutely (if given and greater).

As a convenience, when neither argument is a float, and for
non-finite floats, exact equality is demanded. Also, nan==nan
as far as this function is concerned.

Returns None on success and an error message on failure.
"""

Expand Down Expand Up @@ -232,6 +240,7 @@ def ftest(self, name, got, expected, ulp_tol=5, abs_tol=0.0):
"""Compare arguments expected and got, as floats, if either
is a float, using a tolerance expressed in multiples of
ulp(expected) or absolutely, whichever is greater.

As a convenience, when neither argument is a float, and for
non-finite floats, exact equality is demanded. Also, nan==nan
in this function.
Expand Down Expand Up @@ -492,17 +501,11 @@ def testFactorial(self):
self.assertRaises(ValueError, math.factorial, -1)
self.assertRaises(ValueError, math.factorial, -10**100)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def testFactorialNonIntegers(self):
with self.assertWarns(DeprecationWarning):
self.assertEqual(math.factorial(5.0), 120)
with self.assertWarns(DeprecationWarning):
self.assertRaises(ValueError, math.factorial, 5.2)
with self.assertWarns(DeprecationWarning):
self.assertRaises(ValueError, math.factorial, -1.0)
with self.assertWarns(DeprecationWarning):
self.assertRaises(ValueError, math.factorial, -1e100)
self.assertRaises(TypeError, math.factorial, 5.0)
self.assertRaises(TypeError, math.factorial, 5.2)
self.assertRaises(TypeError, math.factorial, -1.0)
self.assertRaises(TypeError, math.factorial, -1e100)
self.assertRaises(TypeError, math.factorial, decimal.Decimal('5'))
self.assertRaises(TypeError, math.factorial, decimal.Decimal('5.2'))
self.assertRaises(TypeError, math.factorial, "5")
Expand All @@ -513,8 +516,7 @@ def testFactorialHugeInputs(self):
# Currently raises OverflowError for inputs that are too large
# to fit into a C long.
self.assertRaises(OverflowError, math.factorial, 10**100)
with self.assertWarns(DeprecationWarning):
self.assertRaises(OverflowError, math.factorial, 1e100)
self.assertRaises(TypeError, math.factorial, 1e100)

def testFloor(self):
self.assertRaises(TypeError, math.floor)
Expand Down Expand Up @@ -587,7 +589,6 @@ def testfrexp(name, result, expected):
self.assertEqual(math.frexp(NINF)[0], NINF)
self.assertTrue(math.isnan(math.frexp(NAN)[0]))


@requires_IEEE_754
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
"fsum is not exact on machines with double rounding")
Expand All @@ -611,6 +612,7 @@ def msum(iterable):
"""Full precision summation. Compute sum(iterable) without any
intermediate accumulation of error. Based on the 'lsum' function
at http://code.activestate.com/recipes/393090/

"""
tmant, texp = 0, 0
for x in iterable:
Expand Down Expand Up @@ -684,8 +686,6 @@ def msum(iterable):
s = msum(vals)
self.assertEqual(msum(vals), math.fsum(vals))


# Python 3.9
def testGcd(self):
gcd = math.gcd
self.assertEqual(gcd(0, 0), 0)
Expand Down Expand Up @@ -726,7 +726,7 @@ def testGcd(self):
self.assertRaises(TypeError, gcd, 120.0, 84)
self.assertRaises(TypeError, gcd, 120, 84.0)
self.assertRaises(TypeError, gcd, 120, 1, 84.0)
#self.assertEqual(gcd(MyIndexable(120), MyIndexable(84)), 12) # TODO: RUSTPYTHON
self.assertEqual(gcd(MyIndexable(120), MyIndexable(84)), 12)

def testHypot(self):
from decimal import Decimal
Expand Down Expand Up @@ -795,13 +795,79 @@ def testHypot(self):
# Verify scaling for extremely large values
fourthmax = FLOAT_MAX / 4.0
for n in range(32):
self.assertEqual(hypot(*([fourthmax]*n)), fourthmax * math.sqrt(n))
self.assertTrue(math.isclose(hypot(*([fourthmax]*n)),
fourthmax * math.sqrt(n)))

# Verify scaling for extremely small values
for exp in range(32):
scale = FLOAT_MIN / 2.0 ** exp
self.assertEqual(math.hypot(4*scale, 3*scale), 5*scale)

@requires_IEEE_754
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
"hypot() loses accuracy on machines with double rounding")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def testHypotAccuracy(self):
# Verify improved accuracy in cases that were known to be inaccurate.
#
# The new algorithm's accuracy depends on IEEE 754 arithmetic
# guarantees, on having the usual ROUND HALF EVEN rounding mode, on
# the system not having double rounding due to extended precision,
# and on the compiler maintaining the specified order of operations.
#
# This test is known to succeed on most of our builds. If it fails
# some build, we either need to add another skipIf if the cause is
# identifiable; otherwise, we can remove this test entirely.

hypot = math.hypot
Decimal = decimal.Decimal
high_precision = decimal.Context(prec=500)

for hx, hy in [
# Cases with a 1 ulp error in Python 3.7 compiled with Clang
('0x1.10e89518dca48p+29', '0x1.1970f7565b7efp+30'),
('0x1.10106eb4b44a2p+29', '0x1.ef0596cdc97f8p+29'),
('0x1.459c058e20bb7p+30', '0x1.993ca009b9178p+29'),
('0x1.378371ae67c0cp+30', '0x1.fbe6619854b4cp+29'),
('0x1.f4cd0574fb97ap+29', '0x1.50fe31669340ep+30'),
('0x1.494b2cdd3d446p+29', '0x1.212a5367b4c7cp+29'),
('0x1.f84e649f1e46dp+29', '0x1.1fa56bef8eec4p+30'),
('0x1.2e817edd3d6fap+30', '0x1.eb0814f1e9602p+29'),
('0x1.0d3a6e3d04245p+29', '0x1.32a62fea52352p+30'),
('0x1.888e19611bfc5p+29', '0x1.52b8e70b24353p+29'),

# Cases with 2 ulp error in Python 3.8
('0x1.538816d48a13fp+29', '0x1.7967c5ca43e16p+29'),
('0x1.57b47b7234530p+29', '0x1.74e2c7040e772p+29'),
('0x1.821b685e9b168p+30', '0x1.677dc1c1e3dc6p+29'),
('0x1.9e8247f67097bp+29', '0x1.24bd2dc4f4baep+29'),
('0x1.b73b59e0cb5f9p+29', '0x1.da899ab784a97p+28'),
('0x1.94a8d2842a7cfp+30', '0x1.326a51d4d8d8ap+30'),
('0x1.e930b9cd99035p+29', '0x1.5a1030e18dff9p+30'),
('0x1.1592bbb0e4690p+29', '0x1.a9c337b33fb9ap+29'),
('0x1.1243a50751fd4p+29', '0x1.a5a10175622d9p+29'),
('0x1.57a8596e74722p+30', '0x1.42d1af9d04da9p+30'),

# Cases with 1 ulp error in version fff3c28052e6b0
('0x1.ee7dbd9565899p+29', '0x1.7ab4d6fc6e4b4p+29'),
('0x1.5c6bfbec5c4dcp+30', '0x1.02511184b4970p+30'),
('0x1.59dcebba995cap+30', '0x1.50ca7e7c38854p+29'),
('0x1.768cdd94cf5aap+29', '0x1.9cfdc5571d38ep+29'),
('0x1.dcf137d60262ep+29', '0x1.1101621990b3ep+30'),
('0x1.3a2d006e288b0p+30', '0x1.e9a240914326cp+29'),
('0x1.62a32f7f53c61p+29', '0x1.47eb6cd72684fp+29'),
('0x1.d3bcb60748ef2p+29', '0x1.3f13c4056312cp+30'),
('0x1.282bdb82f17f3p+30', '0x1.640ba4c4eed3ap+30'),
('0x1.89d8c423ea0c6p+29', '0x1.d35dcfe902bc3p+29'),
]:
x = float.fromhex(hx)
y = float.fromhex(hy)
with self.subTest(hx=hx, hy=hy, x=x, y=y):
with decimal.localcontext(high_precision):
z = float((Decimal(x)**2 + Decimal(y)**2).sqrt())
self.assertEqual(hypot(x, y), z)

def testDist(self):
from decimal import Decimal as D
from fractions import Fraction as F
Expand Down Expand Up @@ -904,8 +970,8 @@ class T(tuple):
for n in range(32):
p = (fourthmax,) * n
q = (0.0,) * n
self.assertEqual(dist(p, q), fourthmax * math.sqrt(n))
self.assertEqual(dist(q, p), fourthmax * math.sqrt(n))
self.assertTrue(math.isclose(dist(p, q), fourthmax * math.sqrt(n)))
self.assertTrue(math.isclose(dist(q, p), fourthmax * math.sqrt(n)))

# Verify scaling for extremely small values
for exp in range(32):
Expand Down Expand Up @@ -968,8 +1034,7 @@ def __index__(self):
with self.assertRaises(TypeError):
math.isqrt(value)

# Python 3.9
def testlcm(self):
def test_lcm(self):
lcm = math.lcm
self.assertEqual(lcm(0, 0), 0)
self.assertEqual(lcm(1, 0), 0)
Expand Down Expand Up @@ -1011,7 +1076,7 @@ def testlcm(self):
self.assertRaises(TypeError, lcm, 120.0, 84)
self.assertRaises(TypeError, lcm, 120, 84.0)
self.assertRaises(TypeError, lcm, 120, 0, 84.0)
# self.assertEqual(lcm(MyIndexable(120), MyIndexable(84)), 840) # TODO: RUSTPYTHON
self.assertEqual(lcm(MyIndexable(120), MyIndexable(84)), 840)

def testLdexp(self):
self.assertRaises(TypeError, math.ldexp)
Expand Down Expand Up @@ -1987,7 +2052,7 @@ def test_ulp(self):

# min and max
self.assertEqual(math.ulp(0.0),
sys.float_info.min * sys.float_info.epsilon)
sys.float_info.min * sys.float_info.epsilon)
self.assertEqual(math.ulp(FLOAT_MAX),
FLOAT_MAX - math.nextafter(FLOAT_MAX, -INF))

Expand Down Expand Up @@ -2024,6 +2089,7 @@ def assertIsNaN(self, value):

def assertEqualSign(self, x, y):
"""Similar to assertEqual(), but compare also the sign with copysign().

Function useful to compare signed zeros.
"""
self.assertEqual(x, y)
Expand Down