-
-
Notifications
You must be signed in to change notification settings - Fork 35.3k
gh-155526: correct errno handling in complex_abs() #155527
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
base: main
Are you sure you want to change the base?
Changes from all commits
5aa9e73
9702894
576f6cd
31d9fc3
3b851ac
c4f365e
fde8fc7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Correct ``errno`` handling in ``abs(complex)``. Patch by Sergey B | ||
| Kirpichev. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -367,7 +367,7 @@ c_powi(Py_complex x, long n) | |
| double | ||
| _Py_c_abs(Py_complex z) | ||
| { | ||
| /* sets errno = ERANGE on overflow; otherwise errno = 0 */ | ||
| /* sets errno = ERANGE on overflow */ | ||
| double result; | ||
|
|
||
| if (!isfinite(z.real) || !isfinite(z.imag)) { | ||
|
|
@@ -376,23 +376,20 @@ _Py_c_abs(Py_complex z) | |
| NaN. */ | ||
| if (isinf(z.real)) { | ||
| result = fabs(z.real); | ||
| errno = 0; | ||
| return result; | ||
| } | ||
| if (isinf(z.imag)) { | ||
| result = fabs(z.imag); | ||
| errno = 0; | ||
| return result; | ||
| } | ||
| /* either the real or imaginary part is a NaN, | ||
| and neither is infinite. Result should be NaN. */ | ||
| return Py_NAN; | ||
| } | ||
| result = hypot(z.real, z.imag); | ||
| if (!isfinite(result)) | ||
| if (!isfinite(result)) { | ||
| errno = ERANGE; | ||
| else | ||
| errno = 0; | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
|
|
@@ -796,8 +793,13 @@ static PyObject * | |
| complex_abs(PyObject *op) | ||
| { | ||
| PyComplexObject *v = _PyComplexObject_CAST(op); | ||
| double result = _Py_c_abs(v->cval); | ||
| if (errno == ERANGE) { | ||
| double result; | ||
|
|
||
| result = hypot(v->cval.real, v->cval.imag); | ||
| /* Testing FE_OVERFLOW floating-point exception is slow. */ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you mention in the comment that errno is not used on purpose, maybe with a reference to
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about following? We have only one other place, where errno tested for libm's functions. That one removed in #156694. If that PR will be merged, we can add a generic commentary to this file: that we don't test errno values, coming from the library functions. |
||
| if (isfinite(v->cval.real) && isfinite(v->cval.imag) | ||
| && !isfinite(result)) | ||
| { | ||
| PyErr_SetString(PyExc_OverflowError, | ||
| "absolute value too large"); | ||
| return NULL; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer setting
errnoto0sounds risky. With this change, multiplecmathfunction now depends on the currenterrnovalue: polar() and isclose().Since
_Py_c_abs()is our custom API, why not change its API to report the error, rather than relying on the global variableerrno?For example, change the API to
int _Py_c_abs(Py_complex z, double *result): set*resultand return0on success, return-1on error.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, _Py_c_abs() is documented.
An external API should not set errno to zero when the function succeeds. (So, I don't think we should change the documentation to say it does. We should change the implementation.)
The function
polar()setserrno = 0before calling_Py_c_abs(z), so it's OK.The function
isclose()does not readerrno, so it's OK.I suggest: https://github.com/hpkfft/cpython/blob/erange/Objects/complexobject.c#L380-L417
This keeps the documented API, but adds a new function
c_abs()for internal use.If
c_abs()is useful in cmathmodule.c, maybe it needs a better name (and, of course, cannot be static).This can be done as part of #156145
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I forgot that
_Py_c_abs()is part of the public C API (but is private).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a part of the documentation and looks as a mistake. Note that other private complex C-API functions don't do this. This also corresponds to libc behavior. The C standard says:
and
I think that rare C-API users adopt above pattern, like we do in
polar().Though, I'm fine with reversion of that part if you aren't OK with arguments above.
Yes, it seems that errno-free helpers shows some speedup in simple tests (5-10%). But if we decide to change internal API functions in this way, lets do that more systematically, not just for one function.