Skip to content
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

Merged
merged 10 commits into from
Jul 12, 2017
Merged

Handle 32 bit architecture when creating float ranges (#22613) #22644

merged 10 commits into from
Jul 12, 2017

Conversation

angusmoore
Copy link
Contributor

@angusmoore angusmoore commented Jul 1, 2017

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 InexactErrors when linspace, floatrange, etc (see #22613) attempted to round the maximum float returned by maxintfloat to an Int32

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.

@@ -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)
Copy link
Member

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?

Copy link
Member

@ararslan ararslan Jul 1, 2017

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)

Copy link
Contributor Author

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.

Copy link
Member

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!

@ararslan ararslan added collections Data structures holding multiple items, e.g. sets system:32-bit Affects only 32-bit systems backport pending 0.6 labels Jul 1, 2017
@@ -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)
Copy link
Member

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} = ...

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

if Int == Int32
m = min(m, 2147483647.)
end
m = maxintfloat(T ,Int)
Copy link
Member

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

@ararslan ararslan requested a review from timholy July 1, 2017 06:24
Copy link
Member

@ararslan ararslan left a 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. 🙂

@angusmoore
Copy link
Contributor Author

Thanks very much @ararslan, really appreciate your friendly help on my first PR!

Copy link
Member

@timholy timholy left a 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!

@@ -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)
Copy link
Member

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
}

Copy link
Contributor Author

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)))
Copy link
Contributor

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

Copy link
Contributor Author

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).

"""
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.
Copy link
Contributor

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

@@ -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
Copy link
Contributor

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)
Copy link
Member

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).

Copy link
Contributor Author

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.

@ararslan
Copy link
Member

Good to merge?

@angusmoore
Copy link
Contributor Author

Yes, the previous appveyor test failure looked like a timeout, if this one passes, I think this is ready to be merged.

@angusmoore angusmoore changed the title RFC: Handle 32 bit architecture when creating float ranges (#22613) Handle 32 bit architecture when creating float ranges (#22613) Jul 12, 2017
@timholy timholy merged commit f6e55a7 into JuliaLang:master Jul 12, 2017
@timholy
Copy link
Member

timholy commented Jul 12, 2017

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.

@angusmoore angusmoore deleted the anm/ranges-32bit-#22613 branch July 13, 2017 00:11
@tkelman
Copy link
Contributor

tkelman commented Jul 13, 2017

probably should have squash-merged this

@timholy
Copy link
Member

timholy commented Jul 13, 2017

Argh, yes indeed, my apologies.

ararslan pushed a commit that referenced this pull request Sep 11, 2017
Handle 32 bit architecture when creating float ranges (#22613)

Ref #22613
(cherry picked from commit f6e55a7)
ararslan pushed a commit that referenced this pull request Sep 13, 2017
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)
vtjnash pushed a commit that referenced this pull request Sep 14, 2017
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)
ararslan pushed a commit that referenced this pull request Sep 15, 2017
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
collections Data structures holding multiple items, e.g. sets system:32-bit Affects only 32-bit systems
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants