aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPablo Galindo <Pablogsal@gmail.com>2019-03-09 19:18:08 +0000
committerGitHub <noreply@github.com>2019-03-09 19:18:08 +0000
commit0411411c6b16a574144dfb59a7780b057ca8e750 (patch)
treec776766542eb4e21b4e462dc8b84e12a7ab40a3d /Modules/mathmodule.c
parentFix typos and improve grammar in threading.Barrier docstrings (GH-12210) (diff)
downloadcpython-0411411c6b16a574144dfb59a7780b057ca8e750.tar.gz
cpython-0411411c6b16a574144dfb59a7780b057ca8e750.tar.bz2
cpython-0411411c6b16a574144dfb59a7780b057ca8e750.zip
Rework integer overflow path in math.prod and add more tests (GH-11809)
The overflow check was relying on undefined behaviour as it was using the result of the multiplication to do the check, and once the overflow has already happened, any operation on the result is undefined behaviour. Some extra checks that exercise code paths related to this are also added.
Diffstat (limited to 'Modules/mathmodule.c')
-rw-r--r--Modules/mathmodule.c56
1 files changed, 51 insertions, 5 deletions
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index fd0eb327c74..ba8423211c2 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -2493,6 +2493,55 @@ math_isclose_impl(PyObject *module, double a, double b, double rel_tol,
(diff <= abs_tol));
}
+static inline int
+_check_long_mult_overflow(long a, long b) {
+
+ /* From Python2's int_mul code:
+
+ Integer overflow checking for * is painful: Python tried a couple ways, but
+ they didn't work on all platforms, or failed in endcases (a product of
+ -sys.maxint-1 has been a particular pain).
+
+ Here's another way:
+
+ The native long product x*y is either exactly right or *way* off, being
+ just the last n bits of the true product, where n is the number of bits
+ in a long (the delivered product is the true product plus i*2**n for
+ some integer i).
+
+ The native double product (double)x * (double)y is subject to three
+ rounding errors: on a sizeof(long)==8 box, each cast to double can lose
+ info, and even on a sizeof(long)==4 box, the multiplication can lose info.
+ But, unlike the native long product, it's not in *range* trouble: even
+ if sizeof(long)==32 (256-bit longs), the product easily fits in the
+ dynamic range of a double. So the leading 50 (or so) bits of the double
+ product are correct.
+
+ We check these two ways against each other, and declare victory if they're
+ approximately the same. Else, because the native long product is the only
+ one that can lose catastrophic amounts of information, it's the native long
+ product that must have overflowed.
+
+ */
+
+ long longprod = (long)((unsigned long)a * b);
+ double doubleprod = (double)a * (double)b;
+ double doubled_longprod = (double)longprod;
+
+ if (doubled_longprod == doubleprod) {
+ return 0;
+ }
+
+ const double diff = doubled_longprod - doubleprod;
+ const double absdiff = diff >= 0.0 ? diff : -diff;
+ const double absprod = doubleprod >= 0.0 ? doubleprod : -doubleprod;
+
+ if (32.0 * absdiff <= absprod) {
+ return 0;
+ }
+
+ return 1;
+}
/*[clinic input]
math.prod
@@ -2558,11 +2607,8 @@ math_prod_impl(PyObject *module, PyObject *iterable, PyObject *start)
}
if (PyLong_CheckExact(item)) {
long b = PyLong_AsLongAndOverflow(item, &overflow);
- long x = i_result * b;
- /* Continue if there is no overflow */
- if (overflow == 0
- && x < LONG_MAX && x > LONG_MIN
- && !(b != 0 && x / b != i_result)) {
+ if (overflow == 0 && !_check_long_mult_overflow(i_result, b)) {
+ long x = i_result * b;
i_result = x;
Py_DECREF(item);
continue;