Skip to content

Commit 5fd5cb8

Browse files
bpo-38639: Optimize floor(), ceil() and trunc() for floats. (GH-16991)
1 parent 51edf8a commit 5fd5cb8

File tree

3 files changed

+70
-41
lines changed

3 files changed

+70
-41
lines changed

Lib/test/test_math.py

+40-21
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,13 @@ def result_check(expected, got, ulp_tol=5, abs_tol=0.0):
240240
else:
241241
return None
242242

243+
class FloatLike:
244+
def __init__(self, value):
245+
self.value = value
246+
247+
def __float__(self):
248+
return self.value
249+
243250
class IntSubclass(int):
244251
pass
245252

@@ -397,22 +404,29 @@ def testAtan2(self):
397404
def testCeil(self):
398405
self.assertRaises(TypeError, math.ceil)
399406
self.assertEqual(int, type(math.ceil(0.5)))
400-
self.ftest('ceil(0.5)', math.ceil(0.5), 1)
401-
self.ftest('ceil(1.0)', math.ceil(1.0), 1)
402-
self.ftest('ceil(1.5)', math.ceil(1.5), 2)
403-
self.ftest('ceil(-0.5)', math.ceil(-0.5), 0)
404-
self.ftest('ceil(-1.0)', math.ceil(-1.0), -1)
405-
self.ftest('ceil(-1.5)', math.ceil(-1.5), -1)
407+
self.assertEqual(math.ceil(0.5), 1)
408+
self.assertEqual(math.ceil(1.0), 1)
409+
self.assertEqual(math.ceil(1.5), 2)
410+
self.assertEqual(math.ceil(-0.5), 0)
411+
self.assertEqual(math.ceil(-1.0), -1)
412+
self.assertEqual(math.ceil(-1.5), -1)
413+
self.assertEqual(math.ceil(0.0), 0)
414+
self.assertEqual(math.ceil(-0.0), 0)
406415
#self.assertEqual(math.ceil(INF), INF)
407416
#self.assertEqual(math.ceil(NINF), NINF)
408417
#self.assertTrue(math.isnan(math.ceil(NAN)))
409418

410419
class TestCeil:
411420
def __ceil__(self):
412421
return 42
422+
class FloatCeil(float):
423+
def __ceil__(self):
424+
return 42
413425
class TestNoCeil:
414426
pass
415-
self.ftest('ceil(TestCeil())', math.ceil(TestCeil()), 42)
427+
self.assertEqual(math.ceil(TestCeil()), 42)
428+
self.assertEqual(math.ceil(FloatCeil()), 42)
429+
self.assertEqual(math.ceil(FloatLike(42.5)), 43)
416430
self.assertRaises(TypeError, math.ceil, TestNoCeil())
417431

418432
t = TestNoCeil()
@@ -536,26 +550,27 @@ def testFactorialHugeInputs(self):
536550
def testFloor(self):
537551
self.assertRaises(TypeError, math.floor)
538552
self.assertEqual(int, type(math.floor(0.5)))
539-
self.ftest('floor(0.5)', math.floor(0.5), 0)
540-
self.ftest('floor(1.0)', math.floor(1.0), 1)
541-
self.ftest('floor(1.5)', math.floor(1.5), 1)
542-
self.ftest('floor(-0.5)', math.floor(-0.5), -1)
543-
self.ftest('floor(-1.0)', math.floor(-1.0), -1)
544-
self.ftest('floor(-1.5)', math.floor(-1.5), -2)
545-
# pow() relies on floor() to check for integers
546-
# This fails on some platforms - so check it here
547-
self.ftest('floor(1.23e167)', math.floor(1.23e167), 1.23e167)
548-
self.ftest('floor(-1.23e167)', math.floor(-1.23e167), -1.23e167)
553+
self.assertEqual(math.floor(0.5), 0)
554+
self.assertEqual(math.floor(1.0), 1)
555+
self.assertEqual(math.floor(1.5), 1)
556+
self.assertEqual(math.floor(-0.5), -1)
557+
self.assertEqual(math.floor(-1.0), -1)
558+
self.assertEqual(math.floor(-1.5), -2)
549559
#self.assertEqual(math.ceil(INF), INF)
550560
#self.assertEqual(math.ceil(NINF), NINF)
551561
#self.assertTrue(math.isnan(math.floor(NAN)))
552562

553563
class TestFloor:
554564
def __floor__(self):
555565
return 42
566+
class FloatFloor(float):
567+
def __floor__(self):
568+
return 42
556569
class TestNoFloor:
557570
pass
558-
self.ftest('floor(TestFloor())', math.floor(TestFloor()), 42)
571+
self.assertEqual(math.floor(TestFloor()), 42)
572+
self.assertEqual(math.floor(FloatFloor()), 42)
573+
self.assertEqual(math.floor(FloatLike(41.9)), 41)
559574
self.assertRaises(TypeError, math.floor, TestNoFloor())
560575

561576
t = TestNoFloor()
@@ -1448,17 +1463,21 @@ def test_trunc(self):
14481463
self.assertEqual(math.trunc(-0.999999), -0)
14491464
self.assertEqual(math.trunc(-100.999), -100)
14501465

1451-
class TestTrunc(object):
1466+
class TestTrunc:
14521467
def __trunc__(self):
14531468
return 23
1454-
1455-
class TestNoTrunc(object):
1469+
class FloatTrunc(float):
1470+
def __trunc__(self):
1471+
return 23
1472+
class TestNoTrunc:
14561473
pass
14571474

14581475
self.assertEqual(math.trunc(TestTrunc()), 23)
1476+
self.assertEqual(math.trunc(FloatTrunc()), 23)
14591477

14601478
self.assertRaises(TypeError, math.trunc)
14611479
self.assertRaises(TypeError, math.trunc, 1, 2)
1480+
self.assertRaises(TypeError, math.trunc, FloatLike(23.5))
14621481
self.assertRaises(TypeError, math.trunc, TestNoTrunc())
14631482

14641483
def testIsfinite(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Optimized :func:`math.floor()`, :func:`math.ceil()` and :func:`math.trunc()`
2+
for floats.

Modules/mathmodule.c

+28-20
Original file line numberDiff line numberDiff line change
@@ -1013,12 +1013,6 @@ math_1(PyObject *arg, double (*func) (double), int can_overflow)
10131013
return math_1_to_whatever(arg, func, PyFloat_FromDouble, can_overflow);
10141014
}
10151015

1016-
static PyObject *
1017-
math_1_to_int(PyObject *arg, double (*func) (double), int can_overflow)
1018-
{
1019-
return math_1_to_whatever(arg, func, PyLong_FromDouble, can_overflow);
1020-
}
1021-
10221016
static PyObject *
10231017
math_2(PyObject *const *args, Py_ssize_t nargs,
10241018
double (*func) (double, double), const char *funcname)
@@ -1112,17 +1106,22 @@ math_ceil(PyObject *module, PyObject *number)
11121106
/*[clinic end generated code: output=6c3b8a78bc201c67 input=2725352806399cab]*/
11131107
{
11141108
_Py_IDENTIFIER(__ceil__);
1115-
PyObject *method, *result;
11161109

1117-
method = _PyObject_LookupSpecial(number, &PyId___ceil__);
1118-
if (method == NULL) {
1110+
if (!PyFloat_CheckExact(number)) {
1111+
PyObject *method = _PyObject_LookupSpecial(number, &PyId___ceil__);
1112+
if (method != NULL) {
1113+
PyObject *result = _PyObject_CallNoArg(method);
1114+
Py_DECREF(method);
1115+
return result;
1116+
}
11191117
if (PyErr_Occurred())
11201118
return NULL;
1121-
return math_1_to_int(number, ceil, 0);
11221119
}
1123-
result = _PyObject_CallNoArg(method);
1124-
Py_DECREF(method);
1125-
return result;
1120+
double x = PyFloat_AsDouble(number);
1121+
if (x == -1.0 && PyErr_Occurred())
1122+
return NULL;
1123+
1124+
return PyLong_FromDouble(ceil(x));
11261125
}
11271126

11281127
FUNC2(copysign, copysign,
@@ -1170,17 +1169,22 @@ math_floor(PyObject *module, PyObject *number)
11701169
/*[clinic end generated code: output=c6a65c4884884b8a input=63af6b5d7ebcc3d6]*/
11711170
{
11721171
_Py_IDENTIFIER(__floor__);
1173-
PyObject *method, *result;
11741172

1175-
method = _PyObject_LookupSpecial(number, &PyId___floor__);
1176-
if (method == NULL) {
1173+
if (!PyFloat_CheckExact(number)) {
1174+
PyObject *method = _PyObject_LookupSpecial(number, &PyId___floor__);
1175+
if (method != NULL) {
1176+
PyObject *result = _PyObject_CallNoArg(method);
1177+
Py_DECREF(method);
1178+
return result;
1179+
}
11771180
if (PyErr_Occurred())
11781181
return NULL;
1179-
return math_1_to_int(number, floor, 0);
11801182
}
1181-
result = _PyObject_CallNoArg(method);
1182-
Py_DECREF(method);
1183-
return result;
1183+
double x = PyFloat_AsDouble(number);
1184+
if (x == -1.0 && PyErr_Occurred())
1185+
return NULL;
1186+
1187+
return PyLong_FromDouble(floor(x));
11841188
}
11851189

11861190
FUNC1A(gamma, m_tgamma,
@@ -2061,6 +2065,10 @@ math_trunc(PyObject *module, PyObject *x)
20612065
_Py_IDENTIFIER(__trunc__);
20622066
PyObject *trunc, *result;
20632067

2068+
if (PyFloat_CheckExact(x)) {
2069+
return PyFloat_Type.tp_as_number->nb_int(x);
2070+
}
2071+
20642072
if (Py_TYPE(x)->tp_dict == NULL) {
20652073
if (PyType_Ready(Py_TYPE(x)) < 0)
20662074
return NULL;

0 commit comments

Comments
 (0)