forked from Rust-related/RustPython
Updating test_math.py to CPython 3.12.9 (#5507)
* Fixed implementation against CPython 3.12.9 Lib/test/test_math.py tests --------- Signed-off-by: Hanif Ariffin <hanif.ariffin.4326@gmail.com> Co-authored-by: Jeong YunWon <jeong@youknowone.org>
This commit is contained in:
183
Lib/test/ieee754.txt
vendored
Normal file
183
Lib/test/ieee754.txt
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
======================================
|
||||
Python IEEE 754 floating point support
|
||||
======================================
|
||||
|
||||
>>> from sys import float_info as FI
|
||||
>>> from math import *
|
||||
>>> PI = pi
|
||||
>>> E = e
|
||||
|
||||
You must never compare two floats with == because you are not going to get
|
||||
what you expect. We treat two floats as equal if the difference between them
|
||||
is small than epsilon.
|
||||
>>> EPS = 1E-15
|
||||
>>> def equal(x, y):
|
||||
... """Almost equal helper for floats"""
|
||||
... return abs(x - y) < EPS
|
||||
|
||||
|
||||
NaNs and INFs
|
||||
=============
|
||||
|
||||
In Python 2.6 and newer NaNs (not a number) and infinity can be constructed
|
||||
from the strings 'inf' and 'nan'.
|
||||
|
||||
>>> INF = float('inf')
|
||||
>>> NINF = float('-inf')
|
||||
>>> NAN = float('nan')
|
||||
|
||||
>>> INF
|
||||
inf
|
||||
>>> NINF
|
||||
-inf
|
||||
>>> NAN
|
||||
nan
|
||||
|
||||
The math module's ``isnan`` and ``isinf`` functions can be used to detect INF
|
||||
and NAN:
|
||||
>>> isinf(INF), isinf(NINF), isnan(NAN)
|
||||
(True, True, True)
|
||||
>>> INF == -NINF
|
||||
True
|
||||
|
||||
Infinity
|
||||
--------
|
||||
|
||||
Ambiguous operations like ``0 * inf`` or ``inf - inf`` result in NaN.
|
||||
>>> INF * 0
|
||||
nan
|
||||
>>> INF - INF
|
||||
nan
|
||||
>>> INF / INF
|
||||
nan
|
||||
|
||||
However unambiguous operations with inf return inf:
|
||||
>>> INF * INF
|
||||
inf
|
||||
>>> 1.5 * INF
|
||||
inf
|
||||
>>> 0.5 * INF
|
||||
inf
|
||||
>>> INF / 1000
|
||||
inf
|
||||
|
||||
Not a Number
|
||||
------------
|
||||
|
||||
NaNs are never equal to another number, even itself
|
||||
>>> NAN == NAN
|
||||
False
|
||||
>>> NAN < 0
|
||||
False
|
||||
>>> NAN >= 0
|
||||
False
|
||||
|
||||
All operations involving a NaN return a NaN except for nan**0 and 1**nan.
|
||||
>>> 1 + NAN
|
||||
nan
|
||||
>>> 1 * NAN
|
||||
nan
|
||||
>>> 0 * NAN
|
||||
nan
|
||||
>>> 1 ** NAN
|
||||
1.0
|
||||
>>> NAN ** 0
|
||||
1.0
|
||||
>>> 0 ** NAN
|
||||
nan
|
||||
>>> (1.0 + FI.epsilon) * NAN
|
||||
nan
|
||||
|
||||
Misc Functions
|
||||
==============
|
||||
|
||||
The power of 1 raised to x is always 1.0, even for special values like 0,
|
||||
infinity and NaN.
|
||||
|
||||
>>> pow(1, 0)
|
||||
1.0
|
||||
>>> pow(1, INF)
|
||||
1.0
|
||||
>>> pow(1, -INF)
|
||||
1.0
|
||||
>>> pow(1, NAN)
|
||||
1.0
|
||||
|
||||
The power of 0 raised to x is defined as 0, if x is positive. Negative
|
||||
finite values are a domain error or zero division error and NaN result in a
|
||||
silent NaN.
|
||||
|
||||
>>> pow(0, 0)
|
||||
1.0
|
||||
>>> pow(0, INF)
|
||||
0.0
|
||||
>>> pow(0, -INF)
|
||||
inf
|
||||
>>> 0 ** -1
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ZeroDivisionError: 0.0 cannot be raised to a negative power
|
||||
>>> pow(0, NAN)
|
||||
nan
|
||||
|
||||
|
||||
Trigonometric Functions
|
||||
=======================
|
||||
|
||||
>>> sin(INF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> sin(NINF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> sin(NAN)
|
||||
nan
|
||||
>>> cos(INF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> cos(NINF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> cos(NAN)
|
||||
nan
|
||||
>>> tan(INF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> tan(NINF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> tan(NAN)
|
||||
nan
|
||||
|
||||
Neither pi nor tan are exact, but you can assume that tan(pi/2) is a large value
|
||||
and tan(pi) is a very small value:
|
||||
>>> tan(PI/2) > 1E10
|
||||
True
|
||||
>>> -tan(-PI/2) > 1E10
|
||||
True
|
||||
>>> tan(PI) < 1E-15
|
||||
True
|
||||
|
||||
>>> asin(NAN), acos(NAN), atan(NAN)
|
||||
(nan, nan, nan)
|
||||
>>> asin(INF), asin(NINF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> acos(INF), acos(NINF)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: math domain error
|
||||
>>> equal(atan(INF), PI/2), equal(atan(NINF), -PI/2)
|
||||
(True, True)
|
||||
|
||||
|
||||
Hyberbolic Functions
|
||||
====================
|
||||
|
||||
402
Lib/test/test_math.py
vendored
402
Lib/test/test_math.py
vendored
@@ -4,6 +4,7 @@
|
||||
from test.support import verbose, requires_IEEE_754
|
||||
from test import support
|
||||
import unittest
|
||||
import fractions
|
||||
import itertools
|
||||
import decimal
|
||||
import math
|
||||
@@ -186,6 +187,9 @@ def result_check(expected, got, ulp_tol=5, abs_tol=0.0):
|
||||
|
||||
# Check exactly equal (applies also to strings representing exceptions)
|
||||
if got == expected:
|
||||
if not got and not expected:
|
||||
if math.copysign(1, got) != math.copysign(1, expected):
|
||||
return f"expected {expected}, got {got} (zero has wrong sign)"
|
||||
return None
|
||||
|
||||
failure = "not equal"
|
||||
@@ -234,6 +238,10 @@ class MyIndexable(object):
|
||||
def __index__(self):
|
||||
return self.value
|
||||
|
||||
class BadDescr:
|
||||
def __get__(self, obj, objtype=None):
|
||||
raise ValueError
|
||||
|
||||
class MathTests(unittest.TestCase):
|
||||
|
||||
def ftest(self, name, got, expected, ulp_tol=5, abs_tol=0.0):
|
||||
@@ -323,6 +331,7 @@ class MathTests(unittest.TestCase):
|
||||
self.ftest('atan2(0, 1)', math.atan2(0, 1), 0)
|
||||
self.ftest('atan2(1, 1)', math.atan2(1, 1), math.pi/4)
|
||||
self.ftest('atan2(1, 0)', math.atan2(1, 0), math.pi/2)
|
||||
self.ftest('atan2(1, -1)', math.atan2(1, -1), 3*math.pi/4)
|
||||
|
||||
# math.atan2(0, x)
|
||||
self.ftest('atan2(0., -inf)', math.atan2(0., NINF), math.pi)
|
||||
@@ -416,16 +425,22 @@ class MathTests(unittest.TestCase):
|
||||
return 42
|
||||
class TestNoCeil:
|
||||
pass
|
||||
class TestBadCeil:
|
||||
__ceil__ = BadDescr()
|
||||
self.assertEqual(math.ceil(TestCeil()), 42)
|
||||
self.assertEqual(math.ceil(FloatCeil()), 42)
|
||||
self.assertEqual(math.ceil(FloatLike(42.5)), 43)
|
||||
self.assertRaises(TypeError, math.ceil, TestNoCeil())
|
||||
self.assertRaises(ValueError, math.ceil, TestBadCeil())
|
||||
|
||||
t = TestNoCeil()
|
||||
t.__ceil__ = lambda *args: args
|
||||
self.assertRaises(TypeError, math.ceil, t)
|
||||
self.assertRaises(TypeError, math.ceil, t, 0)
|
||||
|
||||
self.assertEqual(math.ceil(FloatLike(+1.0)), +1.0)
|
||||
self.assertEqual(math.ceil(FloatLike(-1.0)), -1.0)
|
||||
|
||||
@requires_IEEE_754
|
||||
def testCopysign(self):
|
||||
self.assertEqual(math.copysign(1, 42), 1.0)
|
||||
@@ -566,16 +581,22 @@ class MathTests(unittest.TestCase):
|
||||
return 42
|
||||
class TestNoFloor:
|
||||
pass
|
||||
class TestBadFloor:
|
||||
__floor__ = BadDescr()
|
||||
self.assertEqual(math.floor(TestFloor()), 42)
|
||||
self.assertEqual(math.floor(FloatFloor()), 42)
|
||||
self.assertEqual(math.floor(FloatLike(41.9)), 41)
|
||||
self.assertRaises(TypeError, math.floor, TestNoFloor())
|
||||
self.assertRaises(ValueError, math.floor, TestBadFloor())
|
||||
|
||||
t = TestNoFloor()
|
||||
t.__floor__ = lambda *args: args
|
||||
self.assertRaises(TypeError, math.floor, t)
|
||||
self.assertRaises(TypeError, math.floor, t, 0)
|
||||
|
||||
self.assertEqual(math.floor(FloatLike(+1.0)), +1.0)
|
||||
self.assertEqual(math.floor(FloatLike(-1.0)), -1.0)
|
||||
|
||||
def testFmod(self):
|
||||
self.assertRaises(TypeError, math.fmod)
|
||||
self.ftest('fmod(10, 1)', math.fmod(10, 1), 0.0)
|
||||
@@ -597,6 +618,7 @@ class MathTests(unittest.TestCase):
|
||||
self.assertEqual(math.fmod(-3.0, NINF), -3.0)
|
||||
self.assertEqual(math.fmod(0.0, 3.0), 0.0)
|
||||
self.assertEqual(math.fmod(0.0, NINF), 0.0)
|
||||
self.assertRaises(ValueError, math.fmod, INF, INF)
|
||||
|
||||
def testFrexp(self):
|
||||
self.assertRaises(TypeError, math.frexp)
|
||||
@@ -638,7 +660,7 @@ class MathTests(unittest.TestCase):
|
||||
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/
|
||||
at https://code.activestate.com/recipes/393090-binary-floating-point-summation-accurate-to-full-p/
|
||||
|
||||
"""
|
||||
tmant, texp = 0, 0
|
||||
@@ -666,6 +688,7 @@ class MathTests(unittest.TestCase):
|
||||
([], 0.0),
|
||||
([0.0], 0.0),
|
||||
([1e100, 1.0, -1e100, 1e-100, 1e50, -1.0, -1e50], 1e-100),
|
||||
([1e100, 1.0, -1e100, 1e-100, 1e50, -1, -1e50], 1e-100),
|
||||
([2.0**53, -0.5, -2.0**-54], 2.0**53-1.0),
|
||||
([2.0**53, 1.0, 2.0**-100], 2.0**53+2.0),
|
||||
([2.0**53+10.0, 1.0, 2.0**-100], 2.0**53+12.0),
|
||||
@@ -713,6 +736,22 @@ class MathTests(unittest.TestCase):
|
||||
s = msum(vals)
|
||||
self.assertEqual(msum(vals), math.fsum(vals))
|
||||
|
||||
self.assertEqual(math.fsum([1.0, math.inf]), math.inf)
|
||||
self.assertTrue(math.isnan(math.fsum([math.nan, 1.0])))
|
||||
self.assertEqual(math.fsum([1e100, FloatLike(1.0), -1e100, 1e-100,
|
||||
1e50, FloatLike(-1.0), -1e50]), 1e-100)
|
||||
self.assertRaises(OverflowError, math.fsum, [1e+308, 1e+308])
|
||||
self.assertRaises(ValueError, math.fsum, [math.inf, -math.inf])
|
||||
self.assertRaises(TypeError, math.fsum, ['spam'])
|
||||
self.assertRaises(TypeError, math.fsum, 1)
|
||||
self.assertRaises(OverflowError, math.fsum, [10**1000])
|
||||
|
||||
def bad_iter():
|
||||
yield 1.0
|
||||
raise ZeroDivisionError
|
||||
|
||||
self.assertRaises(ZeroDivisionError, math.fsum, bad_iter())
|
||||
|
||||
def testGcd(self):
|
||||
gcd = math.gcd
|
||||
self.assertEqual(gcd(0, 0), 0)
|
||||
@@ -773,9 +812,13 @@ class MathTests(unittest.TestCase):
|
||||
# Test allowable types (those with __float__)
|
||||
self.assertEqual(hypot(12.0, 5.0), 13.0)
|
||||
self.assertEqual(hypot(12, 5), 13)
|
||||
self.assertEqual(hypot(0.75, -1), 1.25)
|
||||
self.assertEqual(hypot(-1, 0.75), 1.25)
|
||||
self.assertEqual(hypot(0.75, FloatLike(-1.)), 1.25)
|
||||
self.assertEqual(hypot(FloatLike(-1.), 0.75), 1.25)
|
||||
self.assertEqual(hypot(Decimal(12), Decimal(5)), 13)
|
||||
self.assertEqual(hypot(Fraction(12, 32), Fraction(5, 32)), Fraction(13, 32))
|
||||
self.assertEqual(hypot(bool(1), bool(0), bool(1), bool(1)), math.sqrt(3))
|
||||
self.assertEqual(hypot(True, False, True, True, True), 2.0)
|
||||
|
||||
# Test corner cases
|
||||
self.assertEqual(hypot(0.0, 0.0), 0.0) # Max input is zero
|
||||
@@ -830,6 +873,8 @@ class MathTests(unittest.TestCase):
|
||||
scale = FLOAT_MIN / 2.0 ** exp
|
||||
self.assertEqual(math.hypot(4*scale, 3*scale), 5*scale)
|
||||
|
||||
self.assertRaises(TypeError, math.hypot, *([1.0]*18), 'spam')
|
||||
|
||||
@requires_IEEE_754
|
||||
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
|
||||
"hypot() loses accuracy on machines with double rounding")
|
||||
@@ -922,12 +967,16 @@ class MathTests(unittest.TestCase):
|
||||
# Test allowable types (those with __float__)
|
||||
self.assertEqual(dist((14.0, 1.0), (2.0, -4.0)), 13.0)
|
||||
self.assertEqual(dist((14, 1), (2, -4)), 13)
|
||||
self.assertEqual(dist((FloatLike(14.), 1), (2, -4)), 13)
|
||||
self.assertEqual(dist((11, 1), (FloatLike(-1.), -4)), 13)
|
||||
self.assertEqual(dist((14, FloatLike(-1.)), (2, -6)), 13)
|
||||
self.assertEqual(dist((14, -1), (2, -6)), 13)
|
||||
self.assertEqual(dist((D(14), D(1)), (D(2), D(-4))), D(13))
|
||||
self.assertEqual(dist((F(14, 32), F(1, 32)), (F(2, 32), F(-4, 32))),
|
||||
F(13, 32))
|
||||
self.assertEqual(dist((True, True, False, True, False),
|
||||
(True, False, True, True, False)),
|
||||
sqrt(2.0))
|
||||
self.assertEqual(dist((True, True, False, False, True, True),
|
||||
(True, False, True, False, False, False)),
|
||||
2.0)
|
||||
|
||||
# Test corner cases
|
||||
self.assertEqual(dist((13.25, 12.5, -3.25),
|
||||
@@ -965,6 +1014,8 @@ class MathTests(unittest.TestCase):
|
||||
dist((1, 2, 3, 4), (5, 6, 7))
|
||||
with self.assertRaises(ValueError): # Check dimension agree
|
||||
dist((1, 2, 3), (4, 5, 6, 7))
|
||||
with self.assertRaises(TypeError):
|
||||
dist((1,)*17 + ("spam",), (1,)*18)
|
||||
with self.assertRaises(TypeError): # Rejects invalid types
|
||||
dist("abc", "xyz")
|
||||
int_too_big_for_float = 10 ** (sys.float_info.max_10_exp + 5)
|
||||
@@ -972,6 +1023,16 @@ class MathTests(unittest.TestCase):
|
||||
dist((1, int_too_big_for_float), (2, 3))
|
||||
with self.assertRaises((ValueError, OverflowError)):
|
||||
dist((2, 3), (1, int_too_big_for_float))
|
||||
with self.assertRaises(TypeError):
|
||||
dist((1,), 2)
|
||||
with self.assertRaises(TypeError):
|
||||
dist([1], 2)
|
||||
|
||||
class BadFloat:
|
||||
__float__ = BadDescr()
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
dist([1], [BadFloat()])
|
||||
|
||||
# Verify that the one dimensional case is equivalent to abs()
|
||||
for i in range(20):
|
||||
@@ -1110,6 +1171,7 @@ class MathTests(unittest.TestCase):
|
||||
|
||||
def testLdexp(self):
|
||||
self.assertRaises(TypeError, math.ldexp)
|
||||
self.assertRaises(TypeError, math.ldexp, 2.0, 1.1)
|
||||
self.ftest('ldexp(0,1)', math.ldexp(0,1), 0)
|
||||
self.ftest('ldexp(1,1)', math.ldexp(1,1), 2)
|
||||
self.ftest('ldexp(1,-1)', math.ldexp(1,-1), 0.5)
|
||||
@@ -1142,6 +1204,7 @@ class MathTests(unittest.TestCase):
|
||||
|
||||
def testLog(self):
|
||||
self.assertRaises(TypeError, math.log)
|
||||
self.assertRaises(TypeError, math.log, 1, 2, 3)
|
||||
self.ftest('log(1/e)', math.log(1/math.e), -1)
|
||||
self.ftest('log(1)', math.log(1), 0)
|
||||
self.ftest('log(e)', math.log(math.e), 1)
|
||||
@@ -1152,6 +1215,7 @@ class MathTests(unittest.TestCase):
|
||||
2302.5850929940457)
|
||||
self.assertRaises(ValueError, math.log, -1.5)
|
||||
self.assertRaises(ValueError, math.log, -10**1000)
|
||||
self.assertRaises(ValueError, math.log, 10, -10)
|
||||
self.assertRaises(ValueError, math.log, NINF)
|
||||
self.assertEqual(math.log(INF), INF)
|
||||
self.assertTrue(math.isnan(math.log(NAN)))
|
||||
@@ -1202,6 +1266,277 @@ class MathTests(unittest.TestCase):
|
||||
self.assertEqual(math.log(INF), INF)
|
||||
self.assertTrue(math.isnan(math.log10(NAN)))
|
||||
|
||||
def testSumProd(self):
|
||||
sumprod = math.sumprod
|
||||
Decimal = decimal.Decimal
|
||||
Fraction = fractions.Fraction
|
||||
|
||||
# Core functionality
|
||||
self.assertEqual(sumprod(iter([10, 20, 30]), (1, 2, 3)), 140)
|
||||
self.assertEqual(sumprod([1.5, 2.5], [3.5, 4.5]), 16.5)
|
||||
self.assertEqual(sumprod([], []), 0)
|
||||
self.assertEqual(sumprod([-1], [1.]), -1)
|
||||
self.assertEqual(sumprod([1.], [-1]), -1)
|
||||
|
||||
# Type preservation and coercion
|
||||
for v in [
|
||||
(10, 20, 30),
|
||||
(1.5, -2.5),
|
||||
(Fraction(3, 5), Fraction(4, 5)),
|
||||
(Decimal(3.5), Decimal(4.5)),
|
||||
(2.5, 10), # float/int
|
||||
(2.5, Fraction(3, 5)), # float/fraction
|
||||
(25, Fraction(3, 5)), # int/fraction
|
||||
(25, Decimal(4.5)), # int/decimal
|
||||
]:
|
||||
for p, q in [(v, v), (v, v[::-1])]:
|
||||
with self.subTest(p=p, q=q):
|
||||
expected = sum(p_i * q_i for p_i, q_i in zip(p, q, strict=True))
|
||||
actual = sumprod(p, q)
|
||||
self.assertEqual(expected, actual)
|
||||
self.assertEqual(type(expected), type(actual))
|
||||
|
||||
# Bad arguments
|
||||
self.assertRaises(TypeError, sumprod) # No args
|
||||
self.assertRaises(TypeError, sumprod, []) # One arg
|
||||
self.assertRaises(TypeError, sumprod, [], [], []) # Three args
|
||||
self.assertRaises(TypeError, sumprod, None, [10]) # Non-iterable
|
||||
self.assertRaises(TypeError, sumprod, [10], None) # Non-iterable
|
||||
self.assertRaises(TypeError, sumprod, ['x'], [1.0])
|
||||
|
||||
# Uneven lengths
|
||||
self.assertRaises(ValueError, sumprod, [10, 20], [30])
|
||||
self.assertRaises(ValueError, sumprod, [10], [20, 30])
|
||||
|
||||
# Overflows
|
||||
self.assertEqual(sumprod([10**20], [1]), 10**20)
|
||||
self.assertEqual(sumprod([1], [10**20]), 10**20)
|
||||
self.assertEqual(sumprod([10**10], [10**10]), 10**20)
|
||||
self.assertEqual(sumprod([10**7]*10**5, [10**7]*10**5), 10**19)
|
||||
self.assertRaises(OverflowError, sumprod, [10**1000], [1.0])
|
||||
self.assertRaises(OverflowError, sumprod, [1.0], [10**1000])
|
||||
|
||||
# Error in iterator
|
||||
def raise_after(n):
|
||||
for i in range(n):
|
||||
yield i
|
||||
raise RuntimeError
|
||||
with self.assertRaises(RuntimeError):
|
||||
sumprod(range(10), raise_after(5))
|
||||
with self.assertRaises(RuntimeError):
|
||||
sumprod(raise_after(5), range(10))
|
||||
|
||||
from test.test_iter import BasicIterClass
|
||||
|
||||
self.assertEqual(sumprod(BasicIterClass(1), [1]), 0)
|
||||
self.assertEqual(sumprod([1], BasicIterClass(1)), 0)
|
||||
|
||||
# Error in multiplication
|
||||
class BadMultiply:
|
||||
def __mul__(self, other):
|
||||
raise RuntimeError
|
||||
def __rmul__(self, other):
|
||||
raise RuntimeError
|
||||
with self.assertRaises(RuntimeError):
|
||||
sumprod([10, BadMultiply(), 30], [1, 2, 3])
|
||||
with self.assertRaises(RuntimeError):
|
||||
sumprod([1, 2, 3], [10, BadMultiply(), 30])
|
||||
|
||||
# Error in addition
|
||||
with self.assertRaises(TypeError):
|
||||
sumprod(['abc', 3], [5, 10])
|
||||
with self.assertRaises(TypeError):
|
||||
sumprod([5, 10], ['abc', 3])
|
||||
|
||||
# Special values should give the same as the pure python recipe
|
||||
self.assertEqual(sumprod([10.1, math.inf], [20.2, 30.3]), math.inf)
|
||||
self.assertEqual(sumprod([10.1, math.inf], [math.inf, 30.3]), math.inf)
|
||||
self.assertEqual(sumprod([10.1, math.inf], [math.inf, math.inf]), math.inf)
|
||||
self.assertEqual(sumprod([10.1, -math.inf], [20.2, 30.3]), -math.inf)
|
||||
self.assertTrue(math.isnan(sumprod([10.1, math.inf], [-math.inf, math.inf])))
|
||||
self.assertTrue(math.isnan(sumprod([10.1, math.nan], [20.2, 30.3])))
|
||||
self.assertTrue(math.isnan(sumprod([10.1, math.inf], [math.nan, 30.3])))
|
||||
self.assertTrue(math.isnan(sumprod([10.1, math.inf], [20.3, math.nan])))
|
||||
|
||||
# Error cases that arose during development
|
||||
args = ((-5, -5, 10), (1.5, 4611686018427387904, 2305843009213693952))
|
||||
self.assertEqual(sumprod(*args), 0.0)
|
||||
|
||||
|
||||
@requires_IEEE_754
|
||||
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
|
||||
"sumprod() accuracy not guaranteed on machines with double rounding")
|
||||
@support.cpython_only # Other implementations may choose a different algorithm
|
||||
def test_sumprod_accuracy(self):
|
||||
sumprod = math.sumprod
|
||||
self.assertEqual(sumprod([0.1] * 10, [1]*10), 1.0)
|
||||
self.assertEqual(sumprod([0.1] * 20, [True, False] * 10), 1.0)
|
||||
self.assertEqual(sumprod([True, False] * 10, [0.1] * 20), 1.0)
|
||||
self.assertEqual(sumprod([1.0, 10E100, 1.0, -10E100], [1.0]*4), 2.0)
|
||||
|
||||
@support.requires_resource('cpu')
|
||||
def test_sumprod_stress(self):
|
||||
sumprod = math.sumprod
|
||||
product = itertools.product
|
||||
Decimal = decimal.Decimal
|
||||
Fraction = fractions.Fraction
|
||||
|
||||
class Int(int):
|
||||
def __add__(self, other):
|
||||
return Int(int(self) + int(other))
|
||||
def __mul__(self, other):
|
||||
return Int(int(self) * int(other))
|
||||
__radd__ = __add__
|
||||
__rmul__ = __mul__
|
||||
def __repr__(self):
|
||||
return f'Int({int(self)})'
|
||||
|
||||
class Flt(float):
|
||||
def __add__(self, other):
|
||||
return Int(int(self) + int(other))
|
||||
def __mul__(self, other):
|
||||
return Int(int(self) * int(other))
|
||||
__radd__ = __add__
|
||||
__rmul__ = __mul__
|
||||
def __repr__(self):
|
||||
return f'Flt({int(self)})'
|
||||
|
||||
def baseline_sumprod(p, q):
|
||||
"""This defines the target behavior including exceptions and special values.
|
||||
However, it is subject to rounding errors, so float inputs should be exactly
|
||||
representable with only a few bits.
|
||||
"""
|
||||
total = 0
|
||||
for p_i, q_i in zip(p, q, strict=True):
|
||||
total += p_i * q_i
|
||||
return total
|
||||
|
||||
def run(func, *args):
|
||||
"Make comparing functions easier. Returns error status, type, and result."
|
||||
try:
|
||||
result = func(*args)
|
||||
except (AssertionError, NameError):
|
||||
raise
|
||||
except Exception as e:
|
||||
return type(e), None, 'None'
|
||||
return None, type(result), repr(result)
|
||||
|
||||
pools = [
|
||||
(-5, 10, -2**20, 2**31, 2**40, 2**61, 2**62, 2**80, 1.5, Int(7)),
|
||||
(5.25, -3.5, 4.75, 11.25, 400.5, 0.046875, 0.25, -1.0, -0.078125),
|
||||
(-19.0*2**500, 11*2**1000, -3*2**1500, 17*2*333,
|
||||
5.25, -3.25, -3.0*2**(-333), 3, 2**513),
|
||||
(3.75, 2.5, -1.5, float('inf'), -float('inf'), float('NaN'), 14,
|
||||
9, 3+4j, Flt(13), 0.0),
|
||||
(13.25, -4.25, Decimal('10.5'), Decimal('-2.25'), Fraction(13, 8),
|
||||
Fraction(-11, 16), 4.75 + 0.125j, 97, -41, Int(3)),
|
||||
(Decimal('6.125'), Decimal('12.375'), Decimal('-2.75'), Decimal(0),
|
||||
Decimal('Inf'), -Decimal('Inf'), Decimal('NaN'), 12, 13.5),
|
||||
(-2.0 ** -1000, 11*2**1000, 3, 7, -37*2**32, -2*2**-537, -2*2**-538,
|
||||
2*2**-513),
|
||||
(-7 * 2.0 ** -510, 5 * 2.0 ** -520, 17, -19.0, -6.25),
|
||||
(11.25, -3.75, -0.625, 23.375, True, False, 7, Int(5)),
|
||||
]
|
||||
|
||||
for pool in pools:
|
||||
for size in range(4):
|
||||
for args1 in product(pool, repeat=size):
|
||||
for args2 in product(pool, repeat=size):
|
||||
args = (args1, args2)
|
||||
self.assertEqual(
|
||||
run(baseline_sumprod, *args),
|
||||
run(sumprod, *args),
|
||||
args,
|
||||
)
|
||||
|
||||
@requires_IEEE_754
|
||||
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
|
||||
"sumprod() accuracy not guaranteed on machines with double rounding")
|
||||
@support.cpython_only # Other implementations may choose a different algorithm
|
||||
@support.requires_resource('cpu')
|
||||
def test_sumprod_extended_precision_accuracy(self):
|
||||
import operator
|
||||
from fractions import Fraction
|
||||
from itertools import starmap
|
||||
from collections import namedtuple
|
||||
from math import log2, exp2, fabs
|
||||
from random import choices, uniform, shuffle
|
||||
from statistics import median
|
||||
|
||||
DotExample = namedtuple('DotExample', ('x', 'y', 'target_sumprod', 'condition'))
|
||||
|
||||
def DotExact(x, y):
|
||||
vec1 = map(Fraction, x)
|
||||
vec2 = map(Fraction, y)
|
||||
return sum(starmap(operator.mul, zip(vec1, vec2, strict=True)))
|
||||
|
||||
def Condition(x, y):
|
||||
return 2.0 * DotExact(map(abs, x), map(abs, y)) / abs(DotExact(x, y))
|
||||
|
||||
def linspace(lo, hi, n):
|
||||
width = (hi - lo) / (n - 1)
|
||||
return [lo + width * i for i in range(n)]
|
||||
|
||||
def GenDot(n, c):
|
||||
""" Algorithm 6.1 (GenDot) works as follows. The condition number (5.7) of
|
||||
the dot product xT y is proportional to the degree of cancellation. In
|
||||
order to achieve a prescribed cancellation, we generate the first half of
|
||||
the vectors x and y randomly within a large exponent range. This range is
|
||||
chosen according to the anticipated condition number. The second half of x
|
||||
and y is then constructed choosing xi randomly with decreasing exponent,
|
||||
and calculating yi such that some cancellation occurs. Finally, we permute
|
||||
the vectors x, y randomly and calculate the achieved condition number.
|
||||
"""
|
||||
|
||||
assert n >= 6
|
||||
n2 = n // 2
|
||||
x = [0.0] * n
|
||||
y = [0.0] * n
|
||||
b = log2(c)
|
||||
|
||||
# First half with exponents from 0 to |_b/2_| and random ints in between
|
||||
e = choices(range(int(b/2)), k=n2)
|
||||
e[0] = int(b / 2) + 1
|
||||
e[-1] = 0.0
|
||||
|
||||
x[:n2] = [uniform(-1.0, 1.0) * exp2(p) for p in e]
|
||||
y[:n2] = [uniform(-1.0, 1.0) * exp2(p) for p in e]
|
||||
|
||||
# Second half
|
||||
e = list(map(round, linspace(b/2, 0.0 , n-n2)))
|
||||
for i in range(n2, n):
|
||||
x[i] = uniform(-1.0, 1.0) * exp2(e[i - n2])
|
||||
y[i] = (uniform(-1.0, 1.0) * exp2(e[i - n2]) - DotExact(x, y)) / x[i]
|
||||
|
||||
# Shuffle
|
||||
pairs = list(zip(x, y))
|
||||
shuffle(pairs)
|
||||
x, y = zip(*pairs)
|
||||
|
||||
return DotExample(x, y, DotExact(x, y), Condition(x, y))
|
||||
|
||||
def RelativeError(res, ex):
|
||||
x, y, target_sumprod, condition = ex
|
||||
n = DotExact(list(x) + [-res], list(y) + [1])
|
||||
return fabs(n / target_sumprod)
|
||||
|
||||
def Trial(dotfunc, c, n):
|
||||
ex = GenDot(10, c)
|
||||
res = dotfunc(ex.x, ex.y)
|
||||
return RelativeError(res, ex)
|
||||
|
||||
times = 1000 # Number of trials
|
||||
n = 20 # Length of vectors
|
||||
c = 1e30 # Target condition number
|
||||
|
||||
# If the following test fails, it means that the C math library
|
||||
# implementation of fma() is not compliant with the C99 standard
|
||||
# and is inaccurate. To solve this problem, make a new build
|
||||
# with the symbol UNRELIABLE_FMA defined. That will enable a
|
||||
# slower but accurate code path that avoids the fma() call.
|
||||
relative_err = median(Trial(math.sumprod, c, n) for i in range(times))
|
||||
self.assertLess(relative_err, 1e-16)
|
||||
|
||||
def testModf(self):
|
||||
self.assertRaises(TypeError, math.modf)
|
||||
|
||||
@@ -1235,6 +1570,7 @@ class MathTests(unittest.TestCase):
|
||||
self.assertTrue(math.isnan(math.pow(2, NAN)))
|
||||
self.assertTrue(math.isnan(math.pow(0, NAN)))
|
||||
self.assertEqual(math.pow(1, NAN), 1)
|
||||
self.assertRaises(OverflowError, math.pow, 1e+100, 1e+100)
|
||||
|
||||
# pow(0., x)
|
||||
self.assertEqual(math.pow(0., INF), 0.)
|
||||
@@ -1550,7 +1886,7 @@ class MathTests(unittest.TestCase):
|
||||
try:
|
||||
self.assertTrue(math.isnan(math.tan(INF)))
|
||||
self.assertTrue(math.isnan(math.tan(NINF)))
|
||||
except:
|
||||
except ValueError:
|
||||
self.assertRaises(ValueError, math.tan, INF)
|
||||
self.assertRaises(ValueError, math.tan, NINF)
|
||||
self.assertTrue(math.isnan(math.tan(NAN)))
|
||||
@@ -1591,6 +1927,8 @@ class MathTests(unittest.TestCase):
|
||||
return 23
|
||||
class TestNoTrunc:
|
||||
pass
|
||||
class TestBadTrunc:
|
||||
__trunc__ = BadDescr()
|
||||
|
||||
self.assertEqual(math.trunc(TestTrunc()), 23)
|
||||
self.assertEqual(math.trunc(FloatTrunc()), 23)
|
||||
@@ -1599,6 +1937,7 @@ class MathTests(unittest.TestCase):
|
||||
self.assertRaises(TypeError, math.trunc, 1, 2)
|
||||
self.assertRaises(TypeError, math.trunc, FloatLike(23.5))
|
||||
self.assertRaises(TypeError, math.trunc, TestNoTrunc())
|
||||
self.assertRaises(ValueError, math.trunc, TestBadTrunc())
|
||||
|
||||
def testIsfinite(self):
|
||||
self.assertTrue(math.isfinite(0.0))
|
||||
@@ -1626,11 +1965,11 @@ class MathTests(unittest.TestCase):
|
||||
self.assertFalse(math.isinf(0.))
|
||||
self.assertFalse(math.isinf(1.))
|
||||
|
||||
@requires_IEEE_754
|
||||
def test_nan_constant(self):
|
||||
# `math.nan` must be a quiet NaN with positive sign bit
|
||||
self.assertTrue(math.isnan(math.nan))
|
||||
self.assertEqual(math.copysign(1., math.nan), 1.)
|
||||
|
||||
@requires_IEEE_754
|
||||
def test_inf_constant(self):
|
||||
self.assertTrue(math.isinf(math.inf))
|
||||
self.assertGreater(math.inf, 0.0)
|
||||
@@ -1719,6 +2058,13 @@ class MathTests(unittest.TestCase):
|
||||
except OverflowError:
|
||||
result = 'OverflowError'
|
||||
|
||||
# C99+ says for math.h's sqrt: If the argument is +∞ or ±0, it is
|
||||
# returned, unmodified. On another hand, for csqrt: If z is ±0+0i,
|
||||
# the result is +0+0i. Lets correct zero sign of er to follow
|
||||
# first convention.
|
||||
if id in ['sqrt0002', 'sqrt0003', 'sqrt1001', 'sqrt1023']:
|
||||
er = math.copysign(er, ar)
|
||||
|
||||
# Default tolerances
|
||||
ulp_tol, abs_tol = 5, 0.0
|
||||
|
||||
@@ -1802,6 +2148,8 @@ class MathTests(unittest.TestCase):
|
||||
'\n '.join(failures))
|
||||
|
||||
def test_prod(self):
|
||||
from fractions import Fraction as F
|
||||
|
||||
prod = math.prod
|
||||
self.assertEqual(prod([]), 1)
|
||||
self.assertEqual(prod([], start=5), 5)
|
||||
@@ -1813,6 +2161,14 @@ class MathTests(unittest.TestCase):
|
||||
self.assertEqual(prod([1.0, 2.0, 3.0, 4.0, 5.0]), 120.0)
|
||||
self.assertEqual(prod([1, 2, 3, 4.0, 5.0]), 120.0)
|
||||
self.assertEqual(prod([1.0, 2.0, 3.0, 4, 5]), 120.0)
|
||||
self.assertEqual(prod([1., F(3, 2)]), 1.5)
|
||||
|
||||
# Error in multiplication
|
||||
class BadMultiply:
|
||||
def __rmul__(self, other):
|
||||
raise RuntimeError
|
||||
with self.assertRaises(RuntimeError):
|
||||
prod([10., BadMultiply()])
|
||||
|
||||
# Test overflow in fast-path for integers
|
||||
self.assertEqual(prod([1, 1, 2**32, 1, 1]), 2**32)
|
||||
@@ -2044,11 +2400,20 @@ class MathTests(unittest.TestCase):
|
||||
float.fromhex('0x1.fffffffffffffp-1'))
|
||||
self.assertEqual(math.nextafter(1.0, INF),
|
||||
float.fromhex('0x1.0000000000001p+0'))
|
||||
self.assertEqual(math.nextafter(1.0, -INF, steps=1),
|
||||
float.fromhex('0x1.fffffffffffffp-1'))
|
||||
self.assertEqual(math.nextafter(1.0, INF, steps=1),
|
||||
float.fromhex('0x1.0000000000001p+0'))
|
||||
self.assertEqual(math.nextafter(1.0, -INF, steps=3),
|
||||
float.fromhex('0x1.ffffffffffffdp-1'))
|
||||
self.assertEqual(math.nextafter(1.0, INF, steps=3),
|
||||
float.fromhex('0x1.0000000000003p+0'))
|
||||
|
||||
# x == y: y is returned
|
||||
self.assertEqual(math.nextafter(2.0, 2.0), 2.0)
|
||||
self.assertEqualSign(math.nextafter(-0.0, +0.0), +0.0)
|
||||
self.assertEqualSign(math.nextafter(+0.0, -0.0), -0.0)
|
||||
for steps in range(1, 5):
|
||||
self.assertEqual(math.nextafter(2.0, 2.0, steps=steps), 2.0)
|
||||
self.assertEqualSign(math.nextafter(-0.0, +0.0, steps=steps), +0.0)
|
||||
self.assertEqualSign(math.nextafter(+0.0, -0.0, steps=steps), -0.0)
|
||||
|
||||
# around 0.0
|
||||
smallest_subnormal = sys.float_info.min * sys.float_info.epsilon
|
||||
@@ -2073,6 +2438,11 @@ class MathTests(unittest.TestCase):
|
||||
self.assertIsNaN(math.nextafter(1.0, NAN))
|
||||
self.assertIsNaN(math.nextafter(NAN, NAN))
|
||||
|
||||
self.assertEqual(1.0, math.nextafter(1.0, INF, steps=0))
|
||||
with self.assertRaises(ValueError):
|
||||
math.nextafter(1.0, INF, steps=-1)
|
||||
|
||||
|
||||
@requires_IEEE_754
|
||||
def test_ulp(self):
|
||||
self.assertEqual(math.ulp(1.0), sys.float_info.epsilon)
|
||||
@@ -2112,6 +2482,14 @@ class MathTests(unittest.TestCase):
|
||||
# argument to a float.
|
||||
self.assertFalse(getattr(y, "converted", False))
|
||||
|
||||
def test_input_exceptions(self):
|
||||
self.assertRaises(TypeError, math.exp, "spam")
|
||||
self.assertRaises(TypeError, math.erf, "spam")
|
||||
self.assertRaises(TypeError, math.atan2, "spam", 1.0)
|
||||
self.assertRaises(TypeError, math.atan2, 1.0, "spam")
|
||||
self.assertRaises(TypeError, math.atan2, 1.0)
|
||||
self.assertRaises(TypeError, math.atan2, 1.0, 2.0, 3.0)
|
||||
|
||||
# Custom assertions.
|
||||
|
||||
def assertIsNaN(self, value):
|
||||
@@ -2252,7 +2630,7 @@ class IsCloseTests(unittest.TestCase):
|
||||
|
||||
def load_tests(loader, tests, pattern):
|
||||
from doctest import DocFileSuite
|
||||
# tests.addTest(DocFileSuite("ieee754.txt"))
|
||||
tests.addTest(DocFileSuite("ieee754.txt"))
|
||||
return tests
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user