Repository navigation
Don't rely on errno values, coming from platform's math functions #156145
Description
Feature or enhancement
Proposal:
In the cmath module most functions don't check errno values. See cosh() example below, which set nonzero errno in some cases, but don't read errno (e.g. to detect overflow in libm's cosh/sinh()). Here we essentially assume, that platform implements Annex F of the C standard:
Lines 441 to 490 in 04242c0
| static Py_complex | |
| cmath_cosh_impl(PyObject *module, Py_complex z) | |
| /*[clinic end generated code: output=2e969047da601bdb input=d6b66339e9cc332b]*/ | |
| { | |
| Py_complex r; | |
| double x_minus_one; | |
| /* special treatment for cosh(+/-inf + iy) if y is not a NaN */ | |
| if (!isfinite(z.real) || !isfinite(z.imag)) { | |
| if (isinf(z.real) && isfinite(z.imag) && | |
| (z.imag != 0.)) { | |
| if (z.real > 0) { | |
| r.real = copysign(INF, cos(z.imag)); | |
| r.imag = copysign(INF, sin(z.imag)); | |
| } | |
| else { | |
| r.real = copysign(INF, cos(z.imag)); | |
| r.imag = -copysign(INF, sin(z.imag)); | |
| } | |
| } | |
| else { | |
| r = cosh_special_values[special_type(z.real)] | |
| [special_type(z.imag)]; | |
| } | |
| /* need to set errno = EDOM if y is +/- infinity and x is not | |
| a NaN */ | |
| if (isinf(z.imag) && !isnan(z.real)) | |
| errno = EDOM; | |
| else | |
| errno = 0; | |
| return r; | |
| } | |
| if (fabs(z.real) > CM_LOG_LARGE_DOUBLE) { | |
| /* deal correctly with cases where cosh(z.real) overflows but | |
| cosh(z) does not. */ | |
| x_minus_one = z.real - copysign(1., z.real); | |
| r.real = cos(z.imag) * cosh(x_minus_one) * Py_MATH_E; | |
| r.imag = sin(z.imag) * sinh(x_minus_one) * Py_MATH_E; | |
| } else { | |
| r.real = cos(z.imag) * cosh(z.real); | |
| r.imag = sin(z.imag) * sinh(z.real); | |
| } | |
| /* detect overflow, and set errno accordingly */ | |
| if (isinf(r.real) || isinf(r.imag)) | |
| errno = ERANGE; | |
| else | |
| errno = 0; | |
| return r; | |
| } |
What if we extend this approach to the rest of CPython's floating-point arithmetics? Now this seems natural, as we require that platforms C double has IEEE 754 binary64 format. On practice, that means much more, i.e. that platform supports Annex F (modulo bugs). And we can utilize that, assuming that for invalid input - NaN's produced, for overflows - infinities.
For example, instead of current code of float_pow() that handle finite input
Lines 788 to 801 in 04242c0
| errno = 0; | |
| ix = pow(iv, iw); | |
| _Py_ADJUST_ERANGE1(ix); | |
| if (negate_result) | |
| ix = -ix; | |
| if (errno != 0) { | |
| /* We don't expect any errno value other than ERANGE, but | |
| * the range of libm bugs appears unbounded. | |
| */ | |
| PyErr_SetFromErrno(errno == ERANGE ? PyExc_OverflowError : | |
| PyExc_ValueError); | |
| return NULL; | |
| } |
we could use something much more simple:
ix = pow(iv, iw);
if (negate_result)
ix = -ix;
if (isinf(ix)) {
PyErr_SetString(PyExc_OverflowError,
"float exponentiation result out of range");
return NULL;
}(_Py_ADJUST_ERANGE1 and _Py_ADJUST_ERANGE2 helpers will be removed.)
See recent d.p.o thread for illustration of subtle issues, that can be introduced trying to fix errno, coming from platforms functions. We also have occurring bugreports, when platform libm set errno wrongly, e.g. #153144.
Disclaimer: this issue filled, based on my humble understanding of the proposal by @mdickinson. It seems, there are volunteers to work on this (CC @hpkfft), hence issue was opened.
Has this already been discussed elsewhere?
I have already discussed this feature proposal on Discourse
Links to previous discussion of this feature:
https://discuss.python.org/t/108539/
Activity
hpkfft commentedon Aug 21, 2026
Thank you, Sergey. Note that
errnois thread-local storage which is defined in a different shared library (i.e., notlibpython3.16.so), so there is a performance cost to reading/writing it. I've started exploring avoiding its use incompleobject.c. (This is more than just not relying on the value coming from the platform's math library--it's avoidingerrnoas a communication channel between Python functions in the same translation unit.) Early results for complexabs(z)andresult = z + 1.0on an Intel Xeon using GCC 14.2.0 are as follows:pyperf benchmark code
Code as benchmarked for comment in Issue python#156145
skirpichev commentedon Aug 21, 2026
This will be irrelevant in some cases. For example, patch below replaces errno using in the cmath module by a global variable (err). That's incorrect. Valid version would use module state, etc. But difference already barely noticeable:
Details
Edit:
Unfortunately, using floating-point environment seems to be a poor alternative to testing
errno. Forabs()benchmark below I got:Details
Note, that in some cases we will need to examine either errno or floating-point exceptions. For example, lgamma() of nonpositive integer.
skirpichev commentedon Aug 29, 2026
@hpkfft, let me know if you are still interested in working on this.
No need to do everything in one shot. You can start with changing floatobject.c, for example.