-
-
Notifications
You must be signed in to change notification settings - Fork 11.3k
BUG: sinc
: fix underflow for float16
#27784
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
Conversation
Ah, the NumPy version accepts integer input, so we can't just use |
Seems like we should promote integers to float first. The end result from |
b5938d6
to
ff413a2
Compare
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.
Thanks Lucas! 🙏
FWIW this seems reasonable to me 🙂
Multiplying by |
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.
Maybe worth adding some new tests, (particularly for np.sinc(np.float16(0))
) but the new implementation looks good to me!
Co-authored-by: jakirkham <jakirkham@gmail.com>
fc87f1e
to
1cc3faa
Compare
sinc
: minor improvementssinc
: fix underflow for float16
Co-authored-by: Jake Vanderplas <jakevdp@gmail.com>
Co-authored-by: jakirkham <jakirkham@gmail.com>
sinc
: fix underflow for float16 sinc
: fix underflow for float16
Co-authored-by: jakirkham <jakirkham@gmail.com>
finally green 😁 |
Thanks Lucas and Jake! 🙏 FWIW LGTM |
Thanks everyone!
Can you say what this change changes? Either way, it seems to me like this should get a release note, and if that can briefly explain that change, then it will be easy to decide. (I suspect it's fine. There was also an old PR that I never looked at enough, that moved |
For |
OK, fair. It doesn't actually improve any precision anyway, it really just avoids the invalid value for exact |
Ah true, I suppose 1e20 is enough to get exactly 1.0 for higher precision. |
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.
Thanks, but I think we need to do one more iteration unfortunately...
Additionally, to the above below comments/thoughts, can you please include longdouble
in your test, since I am pretty sure it will fail on some architectures (not sure we have that in CI, though).
numpy/lib/_function_base_impl.py
Outdated
@@ -3827,7 +3827,8 @@ def sinc(x): | |||
|
|||
""" | |||
x = np.asanyarray(x) | |||
y = pi * where(x == 0, 1.0e-20, x) | |||
x = x.astype(np.float64) if np.isdtype(x.dtype, ('bool', 'integral')) else x |
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 would suggest just writing:
x = pi * x
Maybe we can even continue with (or where if you like):
y = sin(x)
x[x == 0] = 1 # or where, or copyto
return y / x
That way, we only guess at 1, which seems like a smaller guess? Although, I didn't quite see through whether spelling it may be slower/require more memory.
I was briefly considering using np.nextafter(..., 1)
for the filling value, which fixes the issue of custom dtypes like ml_dtypes.bfloat16
.
(But, we should likely np.finfo(...).smallest_normal
, even without custom dtypes, it is currently slow and actually currently not defined for all longdouble
s; although, both should probably be changed.)
Now, it turns out that bfloat16
currently returns float64 in the where
(I am not quite sure why, I thought I fixed that!?).
But, I think it still fixes the problem, although it will change the result for ml_dtypes.bfloat64
to be float32 rather than float64 I believe (which may be a good thing, though).
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 was silly of course, we would have to copy things twice...
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'll have to think about it, maybe we can just fix it up later, although smallest_normal
has some issues that make it very slow to use right now (and it may indeed fail).
finfo().eps
works for all NumPy dtypes it seems, so maybe that is the better choice (if we use it for NumPy dtypes only anyway).
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.
@seberg, sorry, I'm not sure what the conclusion is here as to how this PR should change - do you have a concrete suggestion?
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 would lean towards fixing the multiplication to remove the isdtype
. Then use dtype.kind == "f"
(assume that means finfo
works) and go with finfo(dtype).eps
because that seems to work as well, and (whether a thing that should be fixed or not) is currently vastly faster and won't have problems with longdouble on double-double
architectures.
But, you need to sanity check that thought of course and maybe you have a better idea.
EDIT: If dtype != "f"
you would just keep guessing at the old value for compatibility probably).
looks like we might not need to use |
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.
Thanks! Not sure that the 1e-20 for "not a float" is great, but unfortunately, maybe better than nothing.
Let's give it a shot. If anyone has worries about that fallback, I'll be happy to follow up.
I'll ignore that internal compiler error in circleCI... thanks again @lucascolley and everyone! |
While reviewing data-apis/array-api-extra#20, @jakirkham suggested these small improvements to the implementation of
sinc
.If the change from
1.0e-20
tonp.finfo(x.dtype).smallest_normal
is classed as breaking, we can leave that out.