-
-
Notifications
You must be signed in to change notification settings - Fork 10.9k
BUG: fix reciprocal of 0+0j
to be inf+nanj
#17449
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?
Conversation
306792b
to
52e78dc
Compare
const @ftype@ d = in1r + in1i*r; | ||
((@ftype@ *)op1)[0] = 1/d; | ||
((@ftype@ *)op1)[1] = -r/d; | ||
if (in1r == 0 && in1i == 0) { |
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.
I chose to mimic what np.divide
is doing here:
numpy/numpy/core/src/umath/loops.c.src
Lines 2698 to 2702 in f4a4ddd
if (in2r_abs == 0 && in2i_abs == 0) { | |
/* divide by zero should yield a complex inf or nan */ | |
((@ftype@ *)op1)[0] = in1r/in2r_abs; | |
((@ftype@ *)op1)[1] = in1i/in2i_abs; | |
} |
numpy/core/src/umath/loops.c.src
Outdated
((@ftype@ *)op1)[0] = 1/d; | ||
((@ftype@ *)op1)[1] = -r/d; | ||
if (in1r == 0 && in1i == 0) { | ||
/* reciprocal of zero should yield real inf and imaginary nan */ |
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.
I don't think this comment is correct - any result is valid as long as it contains at least one infinity
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.
Good point, I've changed the comment.
52e78dc
to
f89922e
Compare
if (in1r == 0 && in1i == 0) { | ||
/* reciprocal of zero should contain at least one infinity */ | ||
((@ftype@ *)op1)[0] = NPY_INFINITY; | ||
((@ftype@ *)op1)[1] = NPY_NAN; |
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.
I wonder if we can compare to some other reciprocal implementation to make sure we are not missing anything. One thing to keep in mind, is that this should give a division warning I think. That could be achieved using npy_set_floatstatus_divbyzero()
I think. But I am a bit curious if there is a way to spell it out differently, even if reordering below and testing if 1/d
is an infinity.
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.
I thought our policy was to not emit a warning where we can set a value instead.
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.
Sorry, yes... I was being silly it is only an issue when a new NaN is created, but here an infinity is created and the NaN is just a funny side-product.
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.
Hmmm, no wait:
In [6]: np.divide([1.], [0])
<ipython-input-6-7923c6b44821>:1: RuntimeWarning: divide by zero encountered in true_divide
np.divide([1.], [0])
Further:
In [8]: np.divide([1.], [-0.])
<ipython-input-8-c274e82a14bd>:1: RuntimeWarning: divide by zero encountered in true_divide
np.divide([1.], [-0.])
Out[8]: array([-inf])
reciprocal of -0.
should probably be -inf
. That said, this is of course already much better than before.
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.
Should I update the reciprocal of -0+0j
and -0-0j
to be -inf+nanj
? If so, I suppose it would be a good idea to update divide as well, since it returns positive infinity in these cases:
>>> numpy.divide([1.], [-0.-0j])
array([inf+nanj])
>>> numpy.divide([1.], [-0.+0j])
array([inf+nanj])
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.
I am confused :/. I am pretty sure it would be best to set the division flags (but maybe we can defer that). As for the -0.
, probably that doesn't really matter... Since the magnitude is infinite and the complex part is NaN
, I am not certain that -inf
would make much sense, since -0-0j
, etc. are also possible causes for infinity.
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.
From what I remember, unlike the reals IEEE complex numbers have no semantic differences between infinities, so it doesn't matter which one we return.
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.
Since inf+nanj
does not behave nicely in Python, would it be better to set the imaginary value to something other than nan? For instance:
>>> 1.0 / (float('inf') + float('nan')*1j)
(nan+nanj)
>>> 1.0 / (float('inf') + 0j)
0j
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.
Sorry, that this stalled. I am still a bit confused on it, but it seems to me:
- It doesn't matter what we return for the imaginary part. NaN may not always be ideal, but it is probably the safest bet. For example, if you calculate the
np.angle
you will get an invalid result and not something that might just be wrong. 1 / 0j
raises aZeroDivisionError
in python, so I think we should add anpy_set_floatstatus_divbyzero()
here, or write the code in a way that ensures setting the flag.
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.
@kurtamohler, Your test is bad there, float('inf') + float('nan')*1j
is not the same as complex(math.inf, math.nan)
due to how multiplication works on complex.
ping. This has stalled. |
I don't remember seeing that conversation. If that is indeed our policy, can we document it somewhere? Either in the ufunc docs, or perhaps in |
I think the floating point error flags for division by zero, overflow, and "invalid" are normally always set when a new NaN or inf is created and never when one is propagated. (Even |
The PR seems pretty far along, but does not yet make sure that the correct warnings are given. The complex division code does this correctly (both divide by zero and invalid value). This also needs tests (which could also just test against |
Sorry, I forgot all about this. I will fix it up in the next couple days |
f89922e
to
fad1498
Compare
This change makes `np.reciprocal(0+0j)` return the same result as `1 / np.array(0+0j)`. See numpy#17425
fad1498
to
6dc9a1b
Compare
I've added the warning and updated the test to check it |
It would be cool to extend the test to all complex dtypes, but the whole file doesn't do this, so I can ignore that part. However, But, besides that small addition, I think this is ready. I am not sure if there is a preference for a specific infinity, but I could not find one so I think it can wait until someone else comes around and wants it. |
@kurtamohler Let us know if you have any questions regarding extending the test. |
This change makes
np.reciprocal(0+0j)
return the same result as1 / np.array(0+0j)
. See #17425