bpo-38639: Optimize floor(), ceil() and trunc() for floats. (GH-16991)
This commit is contained in:
parent
51edf8aaa2
commit
5fd5cb8d85
|
@ -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):
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Optimized :func:`math.floor()`, :func:`math.ceil()` and :func:`math.trunc()`
|
||||||
|
for floats.
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue