| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
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; | |
| } |
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.
I have already discussed this feature proposal on Discourse
https://discuss.python.org/t/108539/
| Back | FazBrowse Home | New Git URL |
Activity
hpkfft commented on Aug 21, 2026
Thank you, Sergey. Note that errno is thread-local storage which is defined in a different shared library (i.e., not libpython3.16.so), so there is a performance cost to reading/writing it. I've started exploring avoiding its use in compleobject.c. (This is more than just not relying on the value coming from the platform's math library--it's avoiding errno as a communication channel between Python functions in the same translation unit.) Early results for complex abs(z) and result = z + 1.0 on an Intel Xeon using GCC 14.2.0 are as follows:
#!/usr/bin/env ./python import time import pyperf z = complex(3.140625, 1.0) def bench_inc(loops, z): range_it = range(loops) t0 = time.perf_counter() for _ in range_it: result = z + 1.0 result = z + 2.0 result = z + 3.0 result = z + 4.0 result = z + 5.0 result = z + 6.0 result = z + 7.0 result = z + 8.0 result = z + 9.0 result = z + 10.0 result = z + 11.0 result = z + 12.0 result = z + 13.0 result = z + 14.0 result = z + 15.0 result = z + 16.0 result = z + 17.0 result = z + 18.0 result = z + 19.0 result = z + 20.0 return time.perf_counter() - t0 runner = pyperf.Runner(values=10, processes=24) runner.bench_func("abs(z)", abs, z) runner.bench_time_func("inc(z)", bench_inc, z, inner_loops=20)Code as benchmarked for comment in Issue python#156145
skirpichev commented on 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:
Edit:
Unfortunately, using floating-point environment seems to be a poor alternative to testing errno. For abs() benchmark below I got:
Note, that in some cases we will need to examine either errno or floating-point exceptions. For example, lgamma() of nonpositive integer.
skirpichev commented on 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.