Skip to content
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

gh-102837: Increase test coverage for the math module #110000

Draft
wants to merge 26 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a1d2c1a
gh-102837: Increase test coverage for the math module
skirpichev Sep 18, 2023
03e8ead
Improve math_2() comment (mention real use-cases for math_2)
skirpichev Sep 18, 2023
26489bb
* trunc: drop _PyType_IsReady() check (L2071) like floor/ceil, L2079
skirpichev Sep 18, 2023
128c81d
* log: L2265
skirpichev Sep 18, 2023
ead9eee
* loghelper: drop inaccessible cases L2234, L2235, L2241. Here arg i…
skirpichev Sep 18, 2023
3a80c78
* dist: L2575, L2577
skirpichev Sep 18, 2023
8b3bb52
* hypot: L2630
skirpichev Sep 18, 2023
4e25c35
* sumprod: L2742, L2752, L2772, L2775, L2779, L2783
skirpichev Sep 19, 2023
7956447
Fix typo in sumprod()
skirpichev Sep 19, 2023
052d7de
* sumprod: L2829, L2833, L2836
skirpichev Sep 19, 2023
b431805
* pow: L2979, L2980
skirpichev Sep 19, 2023
a046568
* prod: L3292, L3306, L3316-3328
skirpichev Sep 19, 2023
d7675d7
Merge branch 'main' into math-cov
skirpichev Sep 28, 2023
96f99cd
Merge branch 'main' into math-cov
skirpichev Oct 6, 2023
3df127e
More efficient (for finite x) handling of special cases in math.modf
skirpichev Oct 6, 2023
5e6d59f
Change error tests in loghelper() to more lightweight versions (x == …
skirpichev Oct 6, 2023
3e8d96f
Explicit tests for non-float objects (amend a1d2c1afbf)
skirpichev Oct 15, 2023
35db224
Amend 8b3bb52420 (use math.sqrt(2) instead)
skirpichev Oct 15, 2023
6844b19
Amend 052d7de86
skirpichev Oct 29, 2023
6037d84
Merge branch 'main' into math-cov
skirpichev Nov 3, 2023
d2c5170
Merge branch 'main' into math-cov
encukou Nov 13, 2023
833d852
Merge branch 'main' into math-cov
serhiy-storchaka Dec 1, 2023
5768395
Merge branch 'main' into math-cov
skirpichev Dec 9, 2023
ad84c4a
Revert PyErr_ExceptionMatches
skirpichev Dec 9, 2023
21bc1b8
Merge branch 'main' into math-cov
skirpichev Apr 2, 2024
d488a1d
Merge branch 'main' into math-cov
skirpichev May 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 55 additions & 0 deletions Lib/test/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,8 @@ def msum(iterable):
([], 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),
([1e100, FloatLike(1.0), -1e100, 1e-100, 1e50, FloatLike(-1.0), -1e50], 1e-100),
skirpichev marked this conversation as resolved.
Show resolved Hide resolved
([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),
Expand Down Expand Up @@ -733,9 +735,18 @@ def msum(iterable):
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.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
Expand Down Expand Up @@ -797,6 +808,8 @@ def testHypot(self):
# Test allowable types (those with __float__)
self.assertEqual(hypot(12.0, 5.0), 13.0)
self.assertEqual(hypot(12, 5), 13)
self.assertEqual(hypot(1, -1), 1.4142135623730951)
self.assertEqual(hypot(1, FloatLike(-1.)), 1.4142135623730951)
skirpichev marked this conversation as resolved.
Show resolved Hide resolved
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))
Expand Down Expand Up @@ -948,6 +961,10 @@ def testDist(self):
# 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)
skirpichev marked this conversation as resolved.
Show resolved Hide resolved
self.assertEqual(dist((14, -1), (2, -6)), 13)
skirpichev marked this conversation as resolved.
Show resolved Hide resolved
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))
Expand Down Expand Up @@ -1005,6 +1022,12 @@ class T(tuple):
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):
p, q = random.random(), random.random()
Expand Down Expand Up @@ -1175,6 +1198,7 @@ def testLdexp(self):

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)
Expand Down Expand Up @@ -1245,6 +1269,9 @@ def testSumProd(self):
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)
self.assertEqual(sumprod([True], [1.0]), 1)

# Type preservation and coercion
for v in [
Expand All @@ -1270,11 +1297,20 @@ def testSumProd(self):
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):
Expand All @@ -1285,6 +1321,11 @@ def raise_after(n):
with self.assertRaises(RuntimeError):
sumprod(raise_after(5), range(10))

from test.test_iter import BasicIterClass
skirpichev marked this conversation as resolved.
Show resolved Hide resolved

self.assertEqual(sumprod(BasicIterClass(1), [1]), 0)
self.assertEqual(sumprod([1], BasicIterClass(1)), 0)

# Error in multiplication
class BadMultiply:
def __mul__(self, other):
Expand Down Expand Up @@ -1523,6 +1564,7 @@ def testPow(self):
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.)
Expand Down Expand Up @@ -1879,6 +1921,8 @@ def __trunc__(self):
return 23
class TestNoTrunc:
pass
class TestBadTrunc:
__trunc__ = BadDescr()

self.assertEqual(math.trunc(TestTrunc()), 23)
self.assertEqual(math.trunc(FloatTrunc()), 23)
Expand All @@ -1887,6 +1931,7 @@ class TestNoTrunc:
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))
Expand Down Expand Up @@ -2087,6 +2132,8 @@ def test_mtestfile(self):
'\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)
Expand All @@ -2098,6 +2145,14 @@ def test_prod(self):
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)
Expand Down
18 changes: 6 additions & 12 deletions Modules/mathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,7 @@ math_1a(PyObject *arg, double (*func) (double))
The last rule is used to catch overflow on platforms which follow
C89 but for which HUGE_VAL is not an infinity.

For most two-argument functions (copysign, fmod, hypot, atan2)
For most two-argument functions (copysign, remainder, atan2)
these rules are enough to ensure that Python's functions behave as
specified in 'Annex F' of the C99 standard, with the 'invalid' and
'divide-by-zero' floating-point exceptions mapping to Python's
Expand Down Expand Up @@ -2068,11 +2068,6 @@ math_trunc(PyObject *module, PyObject *x)
return PyFloat_Type.tp_as_number->nb_int(x);
}

if (!_PyType_IsReady(Py_TYPE(x))) {
skirpichev marked this conversation as resolved.
Show resolved Hide resolved
if (PyType_Ready(Py_TYPE(x)) < 0)
return NULL;
}

math_module_state *state = get_math_module_state(module);
trunc = _PyObject_LookupSpecial(x, state->str___trunc__);
if (trunc == NULL) {
Expand Down Expand Up @@ -2231,14 +2226,12 @@ loghelper(PyObject* arg, double (*func)(double))
}

x = PyLong_AsDouble(arg);
if (x == -1.0 && PyErr_Occurred()) {
if (!PyErr_ExceptionMatches(PyExc_OverflowError))
return NULL;
skirpichev marked this conversation as resolved.
Show resolved Hide resolved
if (PyErr_Occurred()) {
skirpichev marked this conversation as resolved.
Show resolved Hide resolved
/* Here the conversion to double overflowed, but it's possible
to compute the log anyway. Clear the exception and continue. */
PyErr_Clear();
x = _PyLong_Frexp((PyLongObject *)arg, &e);
if (x == -1.0 && PyErr_Occurred())
if (PyErr_Occurred())
skirpichev marked this conversation as resolved.
Show resolved Hide resolved
return NULL;
/* Value is ~= x * 2**e, so the log ~= log(x) + log(2) * e. */
result = func(x) + func(2.0) * e;
Expand Down Expand Up @@ -2830,7 +2823,7 @@ math_sumprod_impl(PyObject *module, PyObject *p, PyObject *q)
PyErr_Clear();
goto finalize_flt_path;
}
} else if (q_type_float && (PyLong_CheckExact(p_i) || PyBool_Check(q_i))) {
} else if (q_type_float && (PyLong_CheckExact(p_i) || PyBool_Check(p_i))) {
serhiy-storchaka marked this conversation as resolved.
Show resolved Hide resolved
flt_q = PyFloat_AS_DOUBLE(q_i);
flt_p = PyLong_AsDouble(p_i);
if (flt_p == -1.0 && PyErr_Occurred()) {
Expand Down Expand Up @@ -2976,7 +2969,8 @@ math_pow_impl(PyObject *module, double x, double y)
(A) (+/-0.)**negative (-> divide-by-zero)
(B) overflow of x**y with x and y finite
*/
else if (Py_IS_INFINITY(r)) {
else {
assert(Py_IS_INFINITY(r));
if (x == 0.)
errno = EDOM;
else
Expand Down