Open skirpichev opened 1 month ago
My proposal: move _Py_ADJUST_ERANGE2() call to _Py_c_pow() and c_powi(). If that does make sense I'll provide a patch.
It makes sense to me. You can add tests to Lib/test/test_capi/test_complex.py
.
I'm working on this. Things are worse than I expect: algorithm for small integer exponents produces (nan+nanj)
, where _Py_c_pow()
- overflows.
>>> pow(1e300+1j, 90.1)
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
pow(1e300+1j, 90.1)
~~~^^^^^^^^^^^^^^^^
OverflowError: complex exponentiation
>>> pow(1e300+1j, 90)
(nan+nanj)
I'm working on this. Things are worse than I expect: algorithm for small integer exponents produces (nan+nanj), where _Py_c_pow() - overflows.
Usually for numbers in Python, in case of doubt, correctness matters more than performance.
Well, do you suggest to wipe out c_powi()
?:) That could solve also #117999, but performance impact is measurable:
$ python3.12 -m timeit -s 'a, b =1+1j, 10' 'pow(a, b)'
1000000 loops, best of 5: 230 nsec per loop
$ python3.12 -m timeit -s 'a, b =1+1j, 10.000001' 'pow(a, b)'
500000 loops, best of 5: 560 nsec per loop
You can catch the overflow using something like that:
diff --git a/Objects/complexobject.c b/Objects/complexobject.c
index 59c84f1359..c90c6805b7 100644
--- a/Objects/complexobject.c
+++ b/Objects/complexobject.c
@@ -164,10 +164,19 @@ c_powu(Py_complex x, long n)
r = c_1;
p = x;
while (mask > 0 && n >= mask) {
- if (n & mask)
+ if (n & mask) {
r = _Py_c_prod(r,p);
+ if (!isfinite(p.real) || !isfinite(p.imag)) {
+ errno = ERANGE;
+ break;
+ }
+ }
mask <<= 1;
p = _Py_c_prod(p,p);
+ if (!isfinite(p.real) || !isfinite(p.imag)) {
+ errno = ERANGE;
+ break;
+ }
}
return r;
}
@@ -551,7 +560,9 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z)
p = _Py_c_pow(a, b);
}
- _Py_ADJUST_ERANGE2(p.real, p.imag);
+ if (errno == 0) {
+ _Py_ADJUST_ERANGE2(p.real, p.imag);
+ }
if (errno == EDOM) {
PyErr_SetString(PyExc_ZeroDivisionError,
"0.0 to a negative or complex power");
Hmm, shouldn't errno set _Py_c_prod()
in this case?
PR is ready for review: https://github.com/python/cpython/pull/120256
Bug report
Bug description:
Right now we do this after invocation of the function or it's optimized alternative (for small integers). That has advantage as - IIUC - both algorithms may trigger error condition. On another hand, behaviour of the public C API function
_Py_c_pow()
(used in the CPython codebase only forcomplex_pow()
) will differ from the pure-python pow()...Other similar functions (
complex_div()
andcomplex_abs()
) leave settingerrno
to corresponding C-API function.My proposal: move
_Py_ADJUST_ERANGE2()
call to_Py_c_pow()
andc_powi()
. If that does make sense I'll provide a patch.CPython versions tested on:
CPython main branch
Operating systems tested on:
No response
Linked PRs