[Python-checkins] bpo-37986: Improve perfomance of PyLong_FromDouble() (GH-15611)

Sergey Fedoseev webhook-mailer at python.org
Sun May 10 05:16:02 EDT 2020


https://github.com/python/cpython/commit/86a93fddf72a2711aca99afa0c5374c8d6b4a321
commit: 86a93fddf72a2711aca99afa0c5374c8d6b4a321
branch: master
author: Sergey Fedoseev <fedoseev.sergey at gmail.com>
committer: GitHub <noreply at github.com>
date: 2020-05-10T10:15:57+01:00
summary:

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 at gmail.com>

files:
A Misc/NEWS.d/next/Core and Builtins/2019-11-20-09-50-58.bpo-37986.o0lmA7.rst
M Objects/floatobject.c
M Objects/longobject.c

diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-11-20-09-50-58.bpo-37986.o0lmA7.rst b/Misc/NEWS.d/next/Core and Builtins/2019-11-20-09-50-58.bpo-37986.o0lmA7.rst
new file mode 100644
index 0000000000000..62446e35ae01b
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2019-11-20-09-50-58.bpo-37986.o0lmA7.rst	
@@ -0,0 +1,2 @@
+Improve performance of :c:func:`PyLong_FromDouble` for values that fit into
+:c:type:`long`.
diff --git a/Objects/floatobject.c b/Objects/floatobject.c
index faa02f2f05795..9f5014092cf20 100644
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -862,27 +862,7 @@ static PyObject *
 float___trunc___impl(PyObject *self)
 /*[clinic end generated code: output=dd3e289dd4c6b538 input=591b9ba0d650fdff]*/
 {
-    double x = PyFloat_AsDouble(self);
-    double wholepart;           /* integral portion of x, rounded toward 0 */
-
-    (void)modf(x, &wholepart);
-    /* Try to get out cheap if this fits in a Python int.  The attempt
-     * to cast to long must be protected, as C doesn't define what
-     * happens if the double is too big to fit in a long.  Some rare
-     * systems raise an exception then (RISCOS was mentioned as one,
-     * and someone using a non-default option on Sun also bumped into
-     * that).  Note that checking for >= and <= LONG_{MIN,MAX} would
-     * still be vulnerable:  if a long has more bits of precision than
-     * a double, casting MIN/MAX to double may yield an approximation,
-     * and if that's rounded up, then, e.g., wholepart=LONG_MAX+1 would
-     * yield true from the C expression wholepart<=LONG_MAX, despite
-     * that wholepart is actually greater than LONG_MAX.
-     */
-    if (LONG_MIN < wholepart && wholepart < LONG_MAX) {
-        const long aslong = (long)wholepart;
-        return PyLong_FromLong(aslong);
-    }
-    return PyLong_FromDouble(wholepart);
+    return PyLong_FromDouble(PyFloat_AS_DOUBLE(self));
 }
 
 /*[clinic input]
diff --git a/Objects/longobject.c b/Objects/longobject.c
index 11fc75b918f77..0ff0e80cd4269 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -416,6 +416,21 @@ PyLong_FromSize_t(size_t ival)
 PyObject *
 PyLong_FromDouble(double dval)
 {
+    /* Try to get out cheap if this fits in a long. When a finite value of real
+     * floating type is converted to an integer type, the value is truncated
+     * toward zero. If the value of the integral part cannot be represented by
+     * the integer type, the behavior is undefined. Thus, we must check that
+     * value is in range (LONG_MIN - 1, LONG_MAX + 1). If a long has more bits
+     * of precision than a double, casting LONG_MIN - 1 to double may yield an
+     * approximation, but LONG_MAX + 1 is a power of two and can be represented
+     * as double exactly (assuming FLT_RADIX is 2 or 16), so for simplicity
+     * check against [-(LONG_MAX + 1), LONG_MAX + 1).
+     */
+    const double int_max = (unsigned long)LONG_MAX + 1;
+    if (-int_max < dval && dval < int_max) {
+        return PyLong_FromLong((long)dval);
+    }
+
     PyLongObject *v;
     double frac;
     int i, ndig, expo, neg;
@@ -435,8 +450,7 @@ PyLong_FromDouble(double dval)
         dval = -dval;
     }
     frac = frexp(dval, &expo); /* dval = frac*2**expo; 0.0 <= frac < 1.0 */
-    if (expo <= 0)
-        return PyLong_FromLong(0L);
+    assert(expo > 0);
     ndig = (expo-1) / PyLong_SHIFT + 1; /* Number of 'digits' in result */
     v = _PyLong_New(ndig);
     if (v == NULL)



More information about the Python-checkins mailing list