Skip to content

Commit 86a93fd

Browse files
bpo-37986: Improve perfomance of PyLong_FromDouble() (GH-15611)
* bpo-37986: Improve perfomance of PyLong_FromDouble() * Use strict bound check for safety and symmetry * Remove possibly outdated performance claims Co-authored-by: Mark Dickinson <dickinsm@gmail.com>
1 parent 1c2fa78 commit 86a93fd

File tree

3 files changed

+19
-23
lines changed

3 files changed

+19
-23
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve performance of :c:func:`PyLong_FromDouble` for values that fit into
2+
:c:type:`long`.

Objects/floatobject.c

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -862,27 +862,7 @@ static PyObject *
862862
float___trunc___impl(PyObject *self)
863863
/*[clinic end generated code: output=dd3e289dd4c6b538 input=591b9ba0d650fdff]*/
864864
{
865-
double x = PyFloat_AsDouble(self);
866-
double wholepart; /* integral portion of x, rounded toward 0 */
867-
868-
(void)modf(x, &wholepart);
869-
/* Try to get out cheap if this fits in a Python int. The attempt
870-
* to cast to long must be protected, as C doesn't define what
871-
* happens if the double is too big to fit in a long. Some rare
872-
* systems raise an exception then (RISCOS was mentioned as one,
873-
* and someone using a non-default option on Sun also bumped into
874-
* that). Note that checking for >= and <= LONG_{MIN,MAX} would
875-
* still be vulnerable: if a long has more bits of precision than
876-
* a double, casting MIN/MAX to double may yield an approximation,
877-
* and if that's rounded up, then, e.g., wholepart=LONG_MAX+1 would
878-
* yield true from the C expression wholepart<=LONG_MAX, despite
879-
* that wholepart is actually greater than LONG_MAX.
880-
*/
881-
if (LONG_MIN < wholepart && wholepart < LONG_MAX) {
882-
const long aslong = (long)wholepart;
883-
return PyLong_FromLong(aslong);
884-
}
885-
return PyLong_FromDouble(wholepart);
865+
return PyLong_FromDouble(PyFloat_AS_DOUBLE(self));
886866
}
887867

888868
/*[clinic input]

Objects/longobject.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,21 @@ PyLong_FromSize_t(size_t ival)
416416
PyObject *
417417
PyLong_FromDouble(double dval)
418418
{
419+
/* Try to get out cheap if this fits in a long. When a finite value of real
420+
* floating type is converted to an integer type, the value is truncated
421+
* toward zero. If the value of the integral part cannot be represented by
422+
* the integer type, the behavior is undefined. Thus, we must check that
423+
* value is in range (LONG_MIN - 1, LONG_MAX + 1). If a long has more bits
424+
* of precision than a double, casting LONG_MIN - 1 to double may yield an
425+
* approximation, but LONG_MAX + 1 is a power of two and can be represented
426+
* as double exactly (assuming FLT_RADIX is 2 or 16), so for simplicity
427+
* check against [-(LONG_MAX + 1), LONG_MAX + 1).
428+
*/
429+
const double int_max = (unsigned long)LONG_MAX + 1;
430+
if (-int_max < dval && dval < int_max) {
431+
return PyLong_FromLong((long)dval);
432+
}
433+
419434
PyLongObject *v;
420435
double frac;
421436
int i, ndig, expo, neg;
@@ -435,8 +450,7 @@ PyLong_FromDouble(double dval)
435450
dval = -dval;
436451
}
437452
frac = frexp(dval, &expo); /* dval = frac*2**expo; 0.0 <= frac < 1.0 */
438-
if (expo <= 0)
439-
return PyLong_FromLong(0L);
453+
assert(expo > 0);
440454
ndig = (expo-1) / PyLong_SHIFT + 1; /* Number of 'digits' in result */
441455
v = _PyLong_New(ndig);
442456
if (v == NULL)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy