-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Refactor rounding (including bugfixes) and document API #50812
Conversation
adding triage label to this to discuss whether Inf/-Inf are valid values for the return. Imo inf can't be a valid representation of a rounded number. |
I am inclined to allow inf for reasons I mentioned in #50778 (comment) and concur that we should discuss this at triage. |
without knowing too much of the details here, I think it could be nice to coordinate with #50730 such that
is always true (or throws an error) |
That would be nice, though hypercomplex numbers can be arbitrarily far from the nearest lattice point. julia> x = Complex(1.9, 1.9)
1.9 + 1.9im
julia> isapprox(x, round(x, RoundDown), atol=1)
false
julia> isapprox(x, round(x, RoundDown), atol=1.3)
true With greater than 4 dimensions it is possible to get this behavior with julia> using Octanians
julia> x = Octonion(fill(1.5, 8)...)
OctonionF64(1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5)
julia> Base.round(x::Octonion, r::RoundingMode) = Octonion(
round(x.s, r),
round(x.v1, r),
round(x.v2, r),
round(x.v3, r),
round(x.v4, r),
round(x.v5, r),
round(x.v6, r),
round(x.v7, r))
julia> y = round(x)
OctonionF64(2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0)
julia> isapprox(x, y, atol=1)
false
julia> isapprox(x, y, atol=1.5)
true |
well, maybe only guarantee that holds for |
This is wrong because infinity isn't an integer. The Julia documentation for The more interesting question is what should be done when the user provides an infinite value as input. FWIW, C/C++ return infinity (with unchanged sign): https://en.cppreference.com/w/cpp/numeric/math/round https://en.cppreference.com/w/cpp/numeric/math/rint https://en.cppreference.com/w/cpp/numeric/math/nearbyint |
Related #42060 |
Could we split the bugfix out so that we can backport it? |
For the record, I don't think this PR should implement #42060, for the reasons given in the discussion there. I only linked it precisely because these methods/implementations are not sound in general. |
The bugfix is implementing a missing method by widening type signatures and adding default fallbacks, so no, this would have to be backported manually. However, the bug can and should be fixed regardless of what we decide about this PR. |
The default fallbacks to fix are only related to #50778, i.e. the methods for |
Nice! My obnoxious corner-case with
is to complain that I would naively expect |
Wouldn't this be dependent on the rounding mode and type being rounded to? For Float types, IEEE semantics for the rounding could dictate when/if |
@imciner2 there are two different meanings of round at play here. The parts of the standard you refer to are concerned with rounding to some value in the floating-point format, while this PR deals with rounding to integer values. The standard also covers that topic, in 5.9, "Details of operations to round a floating-point datum to integral value". Excerpt:
|
In Julia master (but not in any released version) we already have julia> round(Float32, 1e60)
Inf32
julia> round(Float32, floatmax(Float32)-1.0)
3.4028235f38 The method round(::Type{T}, x::Real, r::RoundingMode) where {T} = convert(T, round(x, r)) was added in #45598 without serious consideration about this behavior. It is not too late to revert or revise this definition. |
Considering the discussion in #42060, yes that should probably be reverted & limited to |
Triage disscussed this and while there are some annoyances to how this works, round documents that it's error throwing behaviour is similar to convert, and if convert doesn't error then neither should round. |
Was there discussion about how |
No discussion was made on rounding Irrationals unfortunately. |
What do you mean by "That's the IEEE semantic"? As far as I understand, IEEE specifies that the result should be chosen among the integers (thus among finite values) except if the input is not finite, as mentioned in my message above ("the rounding chooses only from among those floating-point numbers of integral values in the format")? |
… because `digits` and such are not supported in that case
IIUC triage has approved the semantics, but this still needs code review before merging. As for breaking out the bugfixes for backporting, that is possible, however, |
base/float.jl
Outdated
round(x::IEEEFloat, r::RoundingMode{:Down}) = floor_llvm(x) | ||
round(x::IEEEFloat, r::RoundingMode{:Up}) = ceil_llvm(x) | ||
round(x::IEEEFloat, r::RoundingMode{:Nearest}) = rint_llvm(x) | ||
round(::Type{Bool}, x::AbstractFloat, ::RoundingMode{:ToZero}) = |
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.
It seems like these definitions are valid for all Integer
s (using typemin/typemax) is there a reson for special casing these?
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.
tehe I underestimated the generic fallbacks. We don't need these methods at all.
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.
deleted.
Overall this change looks really good. It's always nice to simplify the dispatch hierarchy and come up with the right interface. We probably want a pkgeval to make sure no number packages were defining bad rounding methods that are method ambiguities after this change. |
@nanosoldier |
Can we get #50835 merged before this one, so as not to accidentally introduce breakage there & here? |
The package evaluation job you requested has completed - possible new issues were detected. |
DoubleFloats.jl fails because they don't define The other failures look unrelated. @Seelengrab, I'd rather do t he refactor first and more functionality changes later so that we don't have to keep working with the old system. |
Why was this PR still marked as closing #50812, when it was clear that this change was not clear cut? The revert I mentioned was explicitly to prevent new functionality from being merged. Merging the revert first would have likely enabled this PR to go ahead, without affecting Also, considering the revert was opened two days after this PR, there was ample time to merge it first, before merging the greater refactor. Now we need an actual new PR to re-break the feature this one added :/ |
I've opened the PR for disallowing the complex part, but it would have been nice if that was part of this PR (which explicitly sought ought not to include new functionality, but alas..) |
This PR implements and documents a cohesive, extensible rounding interface. The code that implements it is in base/rounding.jl
And an explanation of how to use it is in docs/src/manual/interfaces.md.
Fixes #50778
Fixes #42060, though maybe this is a bad thing.
Closes #47128
Fixes #51113
This PR also refactors some implementations of rounding for concrete types to use this new interface more naturally.