Skip to content

bpo-45412: Remove Py_SET_ERRNO_ON_MATH_ERROR() macro #28820

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -572,3 +572,13 @@ Removed
Use the new :c:type:`PyConfig` API of the :ref:`Python Initialization Configuration
<init-config>` instead (:pep:`587`).
(Contributed by Victor Stinner in :issue:`44113`.)

* Remove the following math macros using the ``errno`` variable:

* ``Py_ADJUST_ERANGE1()``
* ``Py_ADJUST_ERANGE2()``
* ``Py_OVERFLOWED()``
* ``Py_SET_ERANGE_IF_OVERFLOW()``
* ``Py_SET_ERRNO_ON_MATH_ERROR()``

(Contributed by Victor Stinner in :issue:`45412`.)
73 changes: 73 additions & 0 deletions Include/internal/pycore_pymath.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#ifndef Py_INTERNAL_PYMATH_H
#define Py_INTERNAL_PYMATH_H
#ifdef __cplusplus
extern "C" {
#endif

#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif

/* _Py_ADJUST_ERANGE1(x)
* _Py_ADJUST_ERANGE2(x, y)
* Set errno to 0 before calling a libm function, and invoke one of these
* macros after, passing the function result(s) (_Py_ADJUST_ERANGE2 is useful
* for functions returning complex results). This makes two kinds of
* adjustments to errno: (A) If it looks like the platform libm set
* errno=ERANGE due to underflow, clear errno. (B) If it looks like the
* platform libm overflowed but didn't set errno, force errno to ERANGE. In
* effect, we're trying to force a useful implementation of C89 errno
* behavior.
* Caution:
* This isn't reliable. See Py_OVERFLOWED comments.
* X and Y may be evaluated more than once.
*/
static inline void _Py_ADJUST_ERANGE1(double x)
{
if (errno == 0) {
if (x == Py_HUGE_VAL || x == -Py_HUGE_VAL) {
errno = ERANGE;
}
}
else if (errno == ERANGE && x == 0.0) {
errno = 0;
}
}

static inline void _Py_ADJUST_ERANGE2(double x, double y)
{
if (x == Py_HUGE_VAL || x == -Py_HUGE_VAL ||
y == Py_HUGE_VAL || y == -Py_HUGE_VAL)
{
if (errno == 0) {
errno = ERANGE;
}
}
else if (errno == ERANGE) {
errno = 0;
}
}

// Return whether integral type *type* is signed or not.
#define _Py_IntegralTypeSigned(type) \
((type)(-1) < 0)

// Return the maximum value of integral type *type*.
#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))

#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_PYMATH_H */
46 changes: 0 additions & 46 deletions Include/pymath.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,50 +176,4 @@ PyAPI_FUNC(void) _Py_set_387controlword(unsigned short);
#endif /* __INTEL_COMPILER */
#endif

/* Py_OVERFLOWED(X)
* Return 1 iff a libm function overflowed. Set errno to 0 before calling
* a libm function, and invoke this macro after, passing the function
* result.
* Caution:
* This isn't reliable. C99 no longer requires libm to set errno under
* any exceptional condition, but does require +- HUGE_VAL return
* values on overflow. A 754 box *probably* maps HUGE_VAL to a
* double infinity, and we're cool if that's so, unless the input
* was an infinity and an infinity is the expected result. A C89
* system sets errno to ERANGE, so we check for that too. We're
* out of luck if a C99 754 box doesn't map HUGE_VAL to +Inf, or
* if the returned result is a NaN, or if a C89 box returns HUGE_VAL
* in non-overflow cases.
* X is evaluated more than once.
* Some platforms have better way to spell this, so expect some #ifdef'ery.
*
* OpenBSD uses 'isinf()' because a compiler bug on that platform causes
* the longer macro version to be mis-compiled. This isn't optimal, and
* should be removed once a newer compiler is available on that platform.
* The system that had the failure was running OpenBSD 3.2 on Intel, with
* gcc 2.95.3.
*
* According to Tim's checkin, the FreeBSD systems use isinf() to work
* around a FPE bug on that platform.
*/
#if defined(__FreeBSD__) || defined(__OpenBSD__)
#define Py_OVERFLOWED(X) isinf(X)
#else
#define Py_OVERFLOWED(X) ((X) != 0.0 && (errno == ERANGE || \
(X) == Py_HUGE_VAL || \
(X) == -Py_HUGE_VAL))
#endif

/* Return whether integral type *type* is signed or not. */
#define _Py_IntegralTypeSigned(type) ((type)(-1) < 0)
/* Return the maximum value of integral type *type*. */
#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))

#endif /* Py_PYMATH_H */
63 changes: 0 additions & 63 deletions Include/pyport.h
Original file line number Diff line number Diff line change
Expand Up @@ -316,69 +316,6 @@ extern "C" {
#define Py_SAFE_DOWNCAST(VALUE, WIDE, NARROW) (NARROW)(VALUE)
#endif

/* Py_SET_ERRNO_ON_MATH_ERROR(x)
* If a libm function did not set errno, but it looks like the result
* overflowed or not-a-number, set errno to ERANGE or EDOM. Set errno
* to 0 before calling a libm function, and invoke this macro after,
* passing the function result.
* Caution:
* This isn't reliable. See Py_OVERFLOWED comments.
* X is evaluated more than once.
*/
#if defined(__FreeBSD__) || defined(__OpenBSD__) || (defined(__hpux) && defined(__ia64))
#define _Py_SET_EDOM_FOR_NAN(X) if (isnan(X)) errno = EDOM;
#else
#define _Py_SET_EDOM_FOR_NAN(X) ;
#endif
#define Py_SET_ERRNO_ON_MATH_ERROR(X) \
do { \
if (errno == 0) { \
if ((X) == Py_HUGE_VAL || (X) == -Py_HUGE_VAL) \
errno = ERANGE; \
else _Py_SET_EDOM_FOR_NAN(X) \
} \
} while(0)

/* Py_SET_ERANGE_IF_OVERFLOW(x)
* An alias of Py_SET_ERRNO_ON_MATH_ERROR for backward-compatibility.
*/
#define Py_SET_ERANGE_IF_OVERFLOW(X) Py_SET_ERRNO_ON_MATH_ERROR(X)

/* Py_ADJUST_ERANGE1(x)
* Py_ADJUST_ERANGE2(x, y)
* Set errno to 0 before calling a libm function, and invoke one of these
* macros after, passing the function result(s) (Py_ADJUST_ERANGE2 is useful
* for functions returning complex results). This makes two kinds of
* adjustments to errno: (A) If it looks like the platform libm set
* errno=ERANGE due to underflow, clear errno. (B) If it looks like the
* platform libm overflowed but didn't set errno, force errno to ERANGE. In
* effect, we're trying to force a useful implementation of C89 errno
* behavior.
* Caution:
* This isn't reliable. See Py_OVERFLOWED comments.
* X and Y may be evaluated more than once.
*/
#define Py_ADJUST_ERANGE1(X) \
do { \
if (errno == 0) { \
if ((X) == Py_HUGE_VAL || (X) == -Py_HUGE_VAL) \
errno = ERANGE; \
} \
else if (errno == ERANGE && (X) == 0.0) \
errno = 0; \
} while(0)

#define Py_ADJUST_ERANGE2(X, Y) \
do { \
if ((X) == Py_HUGE_VAL || (X) == -Py_HUGE_VAL || \
(Y) == Py_HUGE_VAL || (Y) == -Py_HUGE_VAL) { \
if (errno == 0) \
errno = ERANGE; \
} \
else if (errno == ERANGE) \
errno = 0; \
} while(0)

/* The functions _Py_dg_strtod and _Py_dg_dtoa in Python/dtoa.c (which are
* required to support the short float repr introduced in Python 3.1) require
* that the floating-point unit that's being used for arithmetic operations
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Remove the following math macros using the ``errno`` variable:

* ``Py_ADJUST_ERANGE1()``
* ``Py_ADJUST_ERANGE2()``
* ``Py_OVERFLOWED()``
* ``Py_SET_ERANGE_IF_OVERFLOW()``
* ``Py_SET_ERRNO_ON_MATH_ERROR()``

Patch by Victor Stinner.
3 changes: 2 additions & 1 deletion Objects/complexobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "Python.h"
#include "pycore_long.h" // _PyLong_GetZero()
#include "pycore_object.h" // _PyObject_Init()
#include "pycore_pymath.h" // _Py_ADJUST_ERANGE2()
#include "structmember.h" // PyMemberDef


Expand Down Expand Up @@ -525,7 +526,7 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z)
p = _Py_c_pow(a, b);
}

Py_ADJUST_ERANGE2(p.real, p.imag);
_Py_ADJUST_ERANGE2(p.real, p.imag);
if (errno == EDOM) {
PyErr_SetString(PyExc_ZeroDivisionError,
"0.0 to a negative or complex power");
Expand Down
3 changes: 2 additions & 1 deletion Objects/floatobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "pycore_interp.h" // _PyInterpreterState.float_state
#include "pycore_long.h" // _PyLong_GetOne()
#include "pycore_object.h" // _PyObject_Init()
#include "pycore_pymath.h" // _Py_ADJUST_ERANGE1()
#include "pycore_pystate.h" // _PyInterpreterState_GET()

#include <ctype.h>
Expand Down Expand Up @@ -809,7 +810,7 @@ float_pow(PyObject *v, PyObject *w, PyObject *z)
*/
errno = 0;
ix = pow(iv, iw);
Py_ADJUST_ERANGE1(ix);
_Py_ADJUST_ERANGE1(ix);
if (negate_result)
ix = -ix;

Expand Down
5 changes: 3 additions & 2 deletions Python/pytime.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#include "Python.h"
#include "pycore_pymath.h" // _Py_InIntegralTypeRange()
#ifdef MS_WINDOWS
#include <winsock2.h> /* struct timeval */
# include <winsock2.h> // struct timeval
#endif

#if defined(__APPLE__)
#include <mach/mach_time.h> /* mach_absolute_time(), mach_timebase_info() */
# include <mach/mach_time.h> // mach_absolute_time(), mach_timebase_info()

#if defined(__APPLE__) && defined(__has_builtin)
# if __has_builtin(__builtin_available)
Expand Down