diff --git a/Include/pymath.h b/Include/pymath.h index f869724334a4c4..fdcb873f3a4d23 100644 --- a/Include/pymath.h +++ b/Include/pymath.h @@ -221,10 +221,27 @@ PyAPI_FUNC(void) _Py_set_387controlword(unsigned short); #define _Py_IntegralTypeMax(type) ((_Py_IntegralTypeSigned(type)) ? (((((type)1 << (sizeof(type)*CHAR_BIT - 2)) - 1) << 1) + 1) : ~(type)0) /* Return the minimum value of integral type *type*. */ #define _Py_IntegralTypeMin(type) ((_Py_IntegralTypeSigned(type)) ? -_Py_IntegralTypeMax(type) - 1 : 0) -/* Check whether *v* is in the range of integral type *type*. This is most - * useful if *v* is floating-point, since demoting a floating-point *v* to an - * integral type that cannot represent *v*'s integral part is undefined - * behavior. */ -#define _Py_InIntegralTypeRange(type, v) (_Py_IntegralTypeMin(type) <= v && v <= _Py_IntegralTypeMax(type)) + +/* Check if the floating-point number v (double) would overflow when casted to + * the integral type 'type'. + * + * Test (double)type_min(type) <= v <= (double)type_max(type) where v is a + * double, and type_min() and type_max() integers are rounded towards zero when + * casted to a double. + * + * (double)int cast rounds to nearest with ties going to nearest even integer + * (ROUND_HALF_EVEN). Use nextafter() to round towards zeros (ROUND_DOWN). + * + * For example, _Py_IntegralTypeMax(int64_t)=2**63-1 casted to double gives + * 2**63 which is greater than 2**63-1. The problem is that "v <= 2**63" fails + * to detect that v will overflow when casted to int64_t. + * nextafter((double)(2**63-1), 0.0) gives the floating-point number 2**63-1024 + * which is less than or equal to the integer 2**63-1 and so can be used to + * test that v would overflow. + * + * In short, nextafter((double)x, 0.0) rounds the integer x towards zero. */ +#define _Py_DoubleInIntegralTypeRange(type, v) \ + (nextafter((double)_Py_IntegralTypeMin(type), 0.0) <= v \ + && v <= nextafter((double)_Py_IntegralTypeMax(type), 0.0)) #endif /* Py_PYMATH_H */ diff --git a/Misc/NEWS.d/next/Library/2020-01-10-10-45-08.bpo-39277.A2qvZ2.rst b/Misc/NEWS.d/next/Library/2020-01-10-10-45-08.bpo-39277.A2qvZ2.rst new file mode 100644 index 00000000000000..2418c8086fb059 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-01-10-10-45-08.bpo-39277.A2qvZ2.rst @@ -0,0 +1,2 @@ +Fix :func:`time.sleep` to properly detect integer overflow when converting a +floating-point number of seconds to an integer. diff --git a/Python/pytime.c b/Python/pytime.c index 54ddfc952b8179..f3e9e94b03c5bb 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -151,7 +151,7 @@ _PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator, } assert(0.0 <= floatpart && floatpart < denominator); - if (!_Py_InIntegralTypeRange(time_t, intpart)) { + if (!_Py_DoubleInIntegralTypeRange(time_t, intpart)) { error_time_t_overflow(); return -1; } @@ -204,7 +204,7 @@ _PyTime_ObjectToTime_t(PyObject *obj, time_t *sec, _PyTime_round_t round) d = _PyTime_Round(d, round); (void)modf(d, &intpart); - if (!_Py_InIntegralTypeRange(time_t, intpart)) { + if (!_Py_DoubleInIntegralTypeRange(time_t, intpart)) { error_time_t_overflow(); return -1; } @@ -389,7 +389,7 @@ _PyTime_FromDouble(_PyTime_t *t, double value, _PyTime_round_t round, d *= (double)unit_to_ns; d = _PyTime_Round(d, round); - if (!_Py_InIntegralTypeRange(_PyTime_t, d)) { + if (!_Py_DoubleInIntegralTypeRange(_PyTime_t, d)) { _PyTime_overflow(); return -1; }