Skip to content

Commit 5dab8e2

Browse files
committed
bpo-37986: Improve perfomance of PyLong_FromDouble()
1 parent be143ec commit 5dab8e2

File tree

3 files changed

+21
-23
lines changed

3 files changed

+21
-23
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Improve performance of :c:func:`PyLong_FromDouble` for values that fit into
2+
:c:type:`long`. Now :meth:`float.__trunc__` is faster up to 10%,
3+
:func:`math.floor()` and :func:`math.ceil()` are faster up to 30% when used
4+
with such values.

Objects/floatobject.c

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -882,27 +882,7 @@ static PyObject *
882882
float___trunc___impl(PyObject *self)
883883
/*[clinic end generated code: output=dd3e289dd4c6b538 input=591b9ba0d650fdff]*/
884884
{
885-
double x = PyFloat_AsDouble(self);
886-
double wholepart; /* integral portion of x, rounded toward 0 */
887-
888-
(void)modf(x, &wholepart);
889-
/* Try to get out cheap if this fits in a Python int. The attempt
890-
* to cast to long must be protected, as C doesn't define what
891-
* happens if the double is too big to fit in a long. Some rare
892-
* systems raise an exception then (RISCOS was mentioned as one,
893-
* and someone using a non-default option on Sun also bumped into
894-
* that). Note that checking for >= and <= LONG_{MIN,MAX} would
895-
* still be vulnerable: if a long has more bits of precision than
896-
* a double, casting MIN/MAX to double may yield an approximation,
897-
* and if that's rounded up, then, e.g., wholepart=LONG_MAX+1 would
898-
* yield true from the C expression wholepart<=LONG_MAX, despite
899-
* that wholepart is actually greater than LONG_MAX.
900-
*/
901-
if (LONG_MIN < wholepart && wholepart < LONG_MAX) {
902-
const long aslong = (long)wholepart;
903-
return PyLong_FromLong(aslong);
904-
}
905-
return PyLong_FromDouble(wholepart);
885+
return PyLong_FromDouble(PyFloat_AS_DOUBLE(self));
906886
}
907887

908888
/* double_round: rounds a finite double to the closest multiple of

Objects/longobject.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,21 @@ PyLong_FromSize_t(size_t ival)
433433
PyObject *
434434
PyLong_FromDouble(double dval)
435435
{
436+
/* Try to get out cheap if this fits in a long. When a finite value of real
437+
* floating type is converted to an integer type, the value is truncated
438+
* toward zero. If the value of the integral part cannot be represented by
439+
* the integer type, the behavior is undefined. Thus, we must check that
440+
* value is in range (LONG_MIN - 1, LONG_MAX + 1). If a long has more bits
441+
* of precision than a double, casting LONG_MIN - 1 to double may yield an
442+
* approximation, but LONG_MAX + 1 is a power of two and can be represented
443+
* as double exactly (assuming FLT_RADIX is 2 or 16), so for simplicity
444+
* check against [-(LONG_MAX + 1), LONG_MAX + 1).
445+
*/
446+
const double int_max = (unsigned long)LONG_MAX + 1;
447+
if (-int_max <= dval && dval < int_max) {
448+
return PyLong_FromLong((long)dval);
449+
}
450+
436451
PyLongObject *v;
437452
double frac;
438453
int i, ndig, expo, neg;
@@ -452,8 +467,7 @@ PyLong_FromDouble(double dval)
452467
dval = -dval;
453468
}
454469
frac = frexp(dval, &expo); /* dval = frac*2**expo; 0.0 <= frac < 1.0 */
455-
if (expo <= 0)
456-
return PyLong_FromLong(0L);
470+
assert(expo > 0);
457471
ndig = (expo-1) / PyLong_SHIFT + 1; /* Number of 'digits' in result */
458472
v = _PyLong_New(ndig);
459473
if (v == NULL)

0 commit comments

Comments
 (0)