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: few coverage nitpicks for the math module #102523

Merged
merged 20 commits into from
Sep 3, 2023
Merged
Show file tree
Hide file tree
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
41 changes: 41 additions & 0 deletions Lib/test/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ def __init__(self, value):
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):
Expand Down Expand Up @@ -324,6 +328,7 @@ def testAtan2(self):
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)
Expand Down Expand Up @@ -417,16 +422,22 @@ def __ceil__(self):
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)
Expand Down Expand Up @@ -567,16 +578,22 @@ def __floor__(self):
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)
Expand All @@ -598,6 +615,7 @@ def testFmod(self):
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)
Expand Down Expand Up @@ -714,6 +732,11 @@ def msum(iterable):
s = msum(vals)
self.assertEqual(msum(vals), math.fsum(vals))

self.assertEqual(math.fsum([1.0, math.inf]), math.inf)
self.assertRaises(OverflowError, math.fsum, [1e+308, 1e+308])
self.assertRaises(ValueError, math.fsum, [math.inf, -math.inf])
self.assertRaises(TypeError, math.fsum, ['spam'])

def testGcd(self):
gcd = math.gcd
self.assertEqual(gcd(0, 0), 0)
Expand Down Expand Up @@ -831,6 +854,8 @@ def testHypot(self):
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")
Expand Down Expand Up @@ -966,13 +991,19 @@ class T(tuple):
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)
with self.assertRaises((ValueError, OverflowError)):
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)

# Verify that the one dimensional case is equivalent to abs()
for i in range(20):
Expand Down Expand Up @@ -1111,6 +1142,7 @@ def test_lcm(self):

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)
Expand Down Expand Up @@ -1153,6 +1185,7 @@ def testLog(self):
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)))
Expand Down Expand Up @@ -2378,6 +2411,14 @@ def __float__(self):
# 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):
Expand Down
17 changes: 7 additions & 10 deletions Modules/mathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2195,12 +2195,10 @@ math_modf_impl(PyObject *module, double x)
double y;
/* some platforms don't do the right thing for NaNs and
infinities, so we take care of special cases directly. */
if (!Py_IS_FINITE(x)) {
if (Py_IS_INFINITY(x))
return Py_BuildValue("(dd)", copysign(0., x), x);
else if (Py_IS_NAN(x))
return Py_BuildValue("(dd)", x, x);
}
if (Py_IS_INFINITY(x))
return Py_BuildValue("(dd)", copysign(0., x), x);
else if (Py_IS_NAN(x))
return Py_BuildValue("(dd)", x, x);
skirpichev marked this conversation as resolved.
Show resolved Hide resolved

errno = 0;
x = modf(x, &y);
Expand Down Expand Up @@ -2950,7 +2948,8 @@ math_pow_impl(PyObject *module, double x, double y)
else /* y < 0. */
r = odd_y ? copysign(0., x) : 0.;
}
else if (Py_IS_INFINITY(y)) {
else {
assert(Py_IS_INFINITY(y));
if (fabs(x) == 1.0)
r = 1.;
else if (y > 0. && fabs(x) > 1.0)
Expand Down Expand Up @@ -3480,9 +3479,7 @@ static const uint8_t factorial_trailing_zeros[] = {
static PyObject *
perm_comb_small(unsigned long long n, unsigned long long k, int iscomb)
{
if (k == 0) {
skirpichev marked this conversation as resolved.
Show resolved Hide resolved
return PyLong_FromLong(1);
}
assert(k != 0);

/* For small enough n and k the result fits in the 64-bit range and can
* be calculated without allocating intermediate PyLong objects. */
Expand Down