bpo-38639: Optimize floor(), ceil() and trunc() for floats. (GH-16991)

This commit is contained in:
Serhiy Storchaka 2019-11-16 18:00:57 +02:00 committed by GitHub
parent 51edf8aaa2
commit 5fd5cb8d85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 41 deletions

View File

@ -240,6 +240,13 @@ def result_check(expected, got, ulp_tol=5, abs_tol=0.0):
else: else:
return None return None
class FloatLike:
def __init__(self, value):
self.value = value
def __float__(self):
return self.value
class IntSubclass(int): class IntSubclass(int):
pass pass
@ -397,12 +404,14 @@ class MathTests(unittest.TestCase):
def testCeil(self): def testCeil(self):
self.assertRaises(TypeError, math.ceil) self.assertRaises(TypeError, math.ceil)
self.assertEqual(int, type(math.ceil(0.5))) self.assertEqual(int, type(math.ceil(0.5)))
self.ftest('ceil(0.5)', math.ceil(0.5), 1) self.assertEqual(math.ceil(0.5), 1)
self.ftest('ceil(1.0)', math.ceil(1.0), 1) self.assertEqual(math.ceil(1.0), 1)
self.ftest('ceil(1.5)', math.ceil(1.5), 2) self.assertEqual(math.ceil(1.5), 2)
self.ftest('ceil(-0.5)', math.ceil(-0.5), 0) self.assertEqual(math.ceil(-0.5), 0)
self.ftest('ceil(-1.0)', math.ceil(-1.0), -1) self.assertEqual(math.ceil(-1.0), -1)
self.ftest('ceil(-1.5)', math.ceil(-1.5), -1) self.assertEqual(math.ceil(-1.5), -1)
self.assertEqual(math.ceil(0.0), 0)
self.assertEqual(math.ceil(-0.0), 0)
#self.assertEqual(math.ceil(INF), INF) #self.assertEqual(math.ceil(INF), INF)
#self.assertEqual(math.ceil(NINF), NINF) #self.assertEqual(math.ceil(NINF), NINF)
#self.assertTrue(math.isnan(math.ceil(NAN))) #self.assertTrue(math.isnan(math.ceil(NAN)))
@ -410,9 +419,14 @@ class MathTests(unittest.TestCase):
class TestCeil: class TestCeil:
def __ceil__(self): def __ceil__(self):
return 42 return 42
class FloatCeil(float):
def __ceil__(self):
return 42
class TestNoCeil: class TestNoCeil:
pass pass
self.ftest('ceil(TestCeil())', math.ceil(TestCeil()), 42) 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(TypeError, math.ceil, TestNoCeil())
t = TestNoCeil() t = TestNoCeil()
@ -536,16 +550,12 @@ class MathTests(unittest.TestCase):
def testFloor(self): def testFloor(self):
self.assertRaises(TypeError, math.floor) self.assertRaises(TypeError, math.floor)
self.assertEqual(int, type(math.floor(0.5))) self.assertEqual(int, type(math.floor(0.5)))
self.ftest('floor(0.5)', math.floor(0.5), 0) self.assertEqual(math.floor(0.5), 0)
self.ftest('floor(1.0)', math.floor(1.0), 1) self.assertEqual(math.floor(1.0), 1)
self.ftest('floor(1.5)', math.floor(1.5), 1) self.assertEqual(math.floor(1.5), 1)
self.ftest('floor(-0.5)', math.floor(-0.5), -1) self.assertEqual(math.floor(-0.5), -1)
self.ftest('floor(-1.0)', math.floor(-1.0), -1) self.assertEqual(math.floor(-1.0), -1)
self.ftest('floor(-1.5)', math.floor(-1.5), -2) self.assertEqual(math.floor(-1.5), -2)
# pow() relies on floor() to check for integers
# This fails on some platforms - so check it here
self.ftest('floor(1.23e167)', math.floor(1.23e167), 1.23e167)
self.ftest('floor(-1.23e167)', math.floor(-1.23e167), -1.23e167)
#self.assertEqual(math.ceil(INF), INF) #self.assertEqual(math.ceil(INF), INF)
#self.assertEqual(math.ceil(NINF), NINF) #self.assertEqual(math.ceil(NINF), NINF)
#self.assertTrue(math.isnan(math.floor(NAN))) #self.assertTrue(math.isnan(math.floor(NAN)))
@ -553,9 +563,14 @@ class MathTests(unittest.TestCase):
class TestFloor: class TestFloor:
def __floor__(self): def __floor__(self):
return 42 return 42
class FloatFloor(float):
def __floor__(self):
return 42
class TestNoFloor: class TestNoFloor:
pass pass
self.ftest('floor(TestFloor())', math.floor(TestFloor()), 42) 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(TypeError, math.floor, TestNoFloor())
t = TestNoFloor() t = TestNoFloor()
@ -1448,17 +1463,21 @@ class MathTests(unittest.TestCase):
self.assertEqual(math.trunc(-0.999999), -0) self.assertEqual(math.trunc(-0.999999), -0)
self.assertEqual(math.trunc(-100.999), -100) self.assertEqual(math.trunc(-100.999), -100)
class TestTrunc(object): class TestTrunc:
def __trunc__(self): def __trunc__(self):
return 23 return 23
class FloatTrunc(float):
class TestNoTrunc(object): def __trunc__(self):
return 23
class TestNoTrunc:
pass pass
self.assertEqual(math.trunc(TestTrunc()), 23) self.assertEqual(math.trunc(TestTrunc()), 23)
self.assertEqual(math.trunc(FloatTrunc()), 23)
self.assertRaises(TypeError, math.trunc) self.assertRaises(TypeError, math.trunc)
self.assertRaises(TypeError, math.trunc, 1, 2) self.assertRaises(TypeError, math.trunc, 1, 2)
self.assertRaises(TypeError, math.trunc, FloatLike(23.5))
self.assertRaises(TypeError, math.trunc, TestNoTrunc()) self.assertRaises(TypeError, math.trunc, TestNoTrunc())
def testIsfinite(self): def testIsfinite(self):

View File

@ -0,0 +1,2 @@
Optimized :func:`math.floor()`, :func:`math.ceil()` and :func:`math.trunc()`
for floats.

View File

@ -1013,12 +1013,6 @@ math_1(PyObject *arg, double (*func) (double), int can_overflow)
return math_1_to_whatever(arg, func, PyFloat_FromDouble, can_overflow); return math_1_to_whatever(arg, func, PyFloat_FromDouble, can_overflow);
} }
static PyObject *
math_1_to_int(PyObject *arg, double (*func) (double), int can_overflow)
{
return math_1_to_whatever(arg, func, PyLong_FromDouble, can_overflow);
}
static PyObject * static PyObject *
math_2(PyObject *const *args, Py_ssize_t nargs, math_2(PyObject *const *args, Py_ssize_t nargs,
double (*func) (double, double), const char *funcname) double (*func) (double, double), const char *funcname)
@ -1112,17 +1106,22 @@ math_ceil(PyObject *module, PyObject *number)
/*[clinic end generated code: output=6c3b8a78bc201c67 input=2725352806399cab]*/ /*[clinic end generated code: output=6c3b8a78bc201c67 input=2725352806399cab]*/
{ {
_Py_IDENTIFIER(__ceil__); _Py_IDENTIFIER(__ceil__);
PyObject *method, *result;
method = _PyObject_LookupSpecial(number, &PyId___ceil__); if (!PyFloat_CheckExact(number)) {
if (method == NULL) { PyObject *method = _PyObject_LookupSpecial(number, &PyId___ceil__);
if (method != NULL) {
PyObject *result = _PyObject_CallNoArg(method);
Py_DECREF(method);
return result;
}
if (PyErr_Occurred()) if (PyErr_Occurred())
return NULL; return NULL;
return math_1_to_int(number, ceil, 0);
} }
result = _PyObject_CallNoArg(method); double x = PyFloat_AsDouble(number);
Py_DECREF(method); if (x == -1.0 && PyErr_Occurred())
return result; return NULL;
return PyLong_FromDouble(ceil(x));
} }
FUNC2(copysign, copysign, FUNC2(copysign, copysign,
@ -1170,17 +1169,22 @@ math_floor(PyObject *module, PyObject *number)
/*[clinic end generated code: output=c6a65c4884884b8a input=63af6b5d7ebcc3d6]*/ /*[clinic end generated code: output=c6a65c4884884b8a input=63af6b5d7ebcc3d6]*/
{ {
_Py_IDENTIFIER(__floor__); _Py_IDENTIFIER(__floor__);
PyObject *method, *result;
method = _PyObject_LookupSpecial(number, &PyId___floor__); if (!PyFloat_CheckExact(number)) {
if (method == NULL) { PyObject *method = _PyObject_LookupSpecial(number, &PyId___floor__);
if (method != NULL) {
PyObject *result = _PyObject_CallNoArg(method);
Py_DECREF(method);
return result;
}
if (PyErr_Occurred()) if (PyErr_Occurred())
return NULL; return NULL;
return math_1_to_int(number, floor, 0);
} }
result = _PyObject_CallNoArg(method); double x = PyFloat_AsDouble(number);
Py_DECREF(method); if (x == -1.0 && PyErr_Occurred())
return result; return NULL;
return PyLong_FromDouble(floor(x));
} }
FUNC1A(gamma, m_tgamma, FUNC1A(gamma, m_tgamma,
@ -2061,6 +2065,10 @@ math_trunc(PyObject *module, PyObject *x)
_Py_IDENTIFIER(__trunc__); _Py_IDENTIFIER(__trunc__);
PyObject *trunc, *result; PyObject *trunc, *result;
if (PyFloat_CheckExact(x)) {
return PyFloat_Type.tp_as_number->nb_int(x);
}
if (Py_TYPE(x)->tp_dict == NULL) { if (Py_TYPE(x)->tp_dict == NULL) {
if (PyType_Ready(Py_TYPE(x)) < 0) if (PyType_Ready(Py_TYPE(x)) < 0)
return NULL; return NULL;