-
-
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
Handle 32 bit architecture when creating float ranges (#22613) #22644
Handle 32 bit architecture when creating float ranges (#22613) #22644
Conversation
base/twiceprecision.jl
Outdated
@@ -107,6 +107,9 @@ end | |||
function floatrange(a::AbstractFloat, st::AbstractFloat, len::Real, divisor::AbstractFloat) | |||
T = promote_type(typeof(a), typeof(st), typeof(divisor)) | |||
m = maxintfloat(T) |
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.
Presumably it's this function that should rather be adjusted?
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.
Hm, maybe not. At any rate, you could use a helper function to avoid having to recheck this every time you call maxintfloat
, e.g.
_maxintfloat(T::Type) = (m = maxintfloat(T); Int === Int32 ? min(m, Float64(typemax(Int))) : m)
(or some better name)
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.
Yes, that's a more code-efficient way of doing the check. I'll update this PR along those lines.
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.
Sounds great. Thanks for working on this!
…erflow on 32 bit.
base/floatfuncs.jl
Outdated
@@ -20,6 +20,7 @@ maxintfloat(::Type{Float64}) = 9007199254740992. | |||
maxintfloat(::Type{Float32}) = Float32(16777216.) | |||
maxintfloat(::Type{Float16}) = Float16(2048f0) | |||
maxintfloat(x::T) where {T<:AbstractFloat} = maxintfloat(T) | |||
maxintfloat(::Type{S}, ::Type{T}) where S <: AbstractFloat where T <: Integer = (m = maxintfloat(S); Int === Int32 ? min(m, S(typemax(T))) : m) |
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.
The typical style for single line function definitions with type parameters is to use a single where
with curly braces, e.g.
maxintfloat(::Type{S}, ::Type{T}) where {S<:AbstractFloat, T<:Integer} = ...
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.
Ah, thank you. I wasn't sure of the syntax for this, and couldn't find many examples elsewhere.
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.
Yeah, it's not really obvious in the documentation and it's hard to know where to look for inspiration in the code. Hopefully we'll find a way to improve the docs in that regard. At any rate, what you have here does work, the shorter form is just preferred for one-liners.
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's good to know, thanks. Looking at it, I prefer the curly brace way because it's much easier to read; I'll default to it in future. I've changed the PR to that style.
base/twiceprecision.jl
Outdated
if Int == Int32 | ||
m = min(m, 2147483647.) | ||
end | ||
m = maxintfloat(T ,Int) |
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.
Got the comma and space permuted here
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.
Looks good to me. Great work here! I've also requested a review from Tim Holy, who knows float ranges far better than I. 🙂
Thanks very much @ararslan, really appreciate your friendly help on my first PR! |
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.
With one small exception, LGTM. Thanks for contributing this!
base/floatfuncs.jl
Outdated
@@ -20,6 +20,7 @@ maxintfloat(::Type{Float64}) = 9007199254740992. | |||
maxintfloat(::Type{Float32}) = Float32(16777216.) | |||
maxintfloat(::Type{Float16}) = Float16(2048f0) | |||
maxintfloat(x::T) where {T<:AbstractFloat} = maxintfloat(T) | |||
maxintfloat(::Type{S}, ::Type{T}) where {S<:AbstractFloat, T<:Integer} = (m = maxintfloat(S); Int === Int32 ? min(m, S(typemax(T))) : m) |
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.
So, if someone types maxintfloat(Float64, Int16)
then I think they'll say this is producing the wrong answer.
The good news is you don't have to work so hard, because LLVM takes care of optimizing this for you:
julia> mymaxintfloat(::Type{S}, ::Type{T}) where {S<:AbstractFloat, T<:Integer} = min(maxintfloat(S), S(typemax(T)))
mymaxintfloat (generic function with 1 method)
julia> mymaxintfloat(Float64, Int32)
2.147483647e9
julia> mymaxintfloat(Float64, Int)
9.007199254740992e15
julia> @code_llvm mymaxintfloat(Float64, Int32)
define double @julia_mymaxintfloat_60880(i8**, i8**) #0 !dbg !5 {
top:
ret double 0x41DFFFFFFFC00000
}
julia> mymaxintfloat(Float64, Int16)
32767.0
julia> @code_llvm mymaxintfloat(Float64, Int16)
define double @julia_mymaxintfloat_60922(i8**, i8**) #0 !dbg !5 {
top:
ret double 3.276700e+04
}
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 Tim. I had considered adding the extra generality, but wasn't sure whether it would slow down the code. Turns out I should have trusted LLVM's cleverness even more. Thanks for these suggestions, very informative!
@@ -20,6 +20,7 @@ maxintfloat(::Type{Float64}) = 9007199254740992. | |||
maxintfloat(::Type{Float32}) = Float32(16777216.) | |||
maxintfloat(::Type{Float16}) = Float16(2048f0) | |||
maxintfloat(x::T) where {T<:AbstractFloat} = maxintfloat(T) | |||
maxintfloat(::Type{S}, ::Type{T}) where {S<:AbstractFloat, T<:Integer} = min(maxintfloat(S), S(typemax(T))) |
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.
may be worth documenting and testing this new signature (including the corner cases that Tim pointed out), since it's exported if it's using the same name
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, good point @tkelman. I've added tests, and I think added documentation (I'm not especially familiar with Documenter, but it passed make docs
and I think looks right).
base/docs/helpdb/Base.jl
Outdated
""" | ||
maxintfloat(T, S) | ||
|
||
The largest integer losslessly representable by the given floating-point DataType `T` that also does not exceed the maximum integer representable by the integer data type T. |
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.
we prefer to wrap long lines in inline docstrings
the integer data type is the second input right? so one should say S
with code highlighting
test/floatfuncs.jl
Outdated
@@ -19,6 +19,10 @@ for elty in (Float16,Float32,Float64) | |||
@test maxintfloat(rand(elty)) == maxintfloat(elty) | |||
end | |||
@test maxintfloat() == maxintfloat(Float64) | |||
@test maxintfloat(Float64, Int32) == 2147483647.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.
these could use ===
which would also test that the return type is as expected
l = linspace(start,stop,n) | ||
@test n == length(l) | ||
# FIXME: these fail some small portion of the time | ||
@test_skip start == first(l) | ||
@test_skip stop == last(l) | ||
end | ||
|
||
# Inexact errors on 32 bit architectures. #22613 | ||
@test first(linspace(log(0.2), log(10.0), 10)) == log(0.2) | ||
@test last(linspace(log(0.2), log(10.0), 10)) == log(10.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.
Probably also worth testing floatrange(-3e9, 1.0, 1, 1.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.
Fair point. I hadn't originally because I incorrectly had in my mind that floatrange
had been deprecated (I was thinking of FloatRange
). I've added a simple test for the bug you identified in the original issue.
Good to merge? |
Yes, the previous appveyor test failure looked like a timeout, if this one passes, I think this is ready to be merged. |
Thanks a ton, @angusmoore! Very nice work. This seems likely to fix JuliaPlots/Plots.jl#968. Since it sounds like it's not possible to run Plots on 32-bit machines, this might be a high-priority backport. |
probably should have squash-merged this |
Argh, yes indeed, my apologies. |
Ref #22644 (cherry picked from commit b9034e7) (cherry picked from commit dd4d8eb) (cherry picked from commit a07f767) (cherry picked from commit f502e1c) (cherry picked from commit ca60ff5) (cherry picked from commit 5018fd6) (cherry picked from commit 8713fa3) (cherry picked from commit 7772517) (cherry picked from commit 4de1a51) (cherry picked from commit 7f163ba)
Ref #22644 (cherry picked from commit b9034e7) (cherry picked from commit dd4d8eb) (cherry picked from commit a07f767) (cherry picked from commit f502e1c) (cherry picked from commit ca60ff5) (cherry picked from commit 5018fd6) (cherry picked from commit 8713fa3) (cherry picked from commit 7772517) (cherry picked from commit 4de1a51) (cherry picked from commit 7f163ba)
Ref #22644 (cherry picked from commit b9034e7) (cherry picked from commit dd4d8eb) (cherry picked from commit a07f767) (cherry picked from commit f502e1c) (cherry picked from commit ca60ff5) (cherry picked from commit 5018fd6) (cherry picked from commit 8713fa3) (cherry picked from commit 7772517) (cherry picked from commit 4de1a51) (cherry picked from commit 7f163ba)
Adds a specialisation of
maxintfloat
that optionally checks (only on 32 bit systems) whether the max float is larger than maximum 32 bit integer. If so, it downsizes the returned float to the max Int32.For background: The maximum 32 bit integer is smaller than the maximum representable integer in a Float64. This caused
InexactError
s when linspace, floatrange, etc (see #22613) attempted to round the maximum float returned bymaxintfloat
to anInt32
I've added a test that reproduces the bug in #22613. This test passes under this branch.
I have also removed a line that skipped some range tests on 32 bit architecture. I assume it was this bug was causing the tests to fail on 32-bit, but it just got commented out and added to the todo pile. With the fix in this PR, these tests passed when I ran them on my 32-bit machine.