diff --git a/base/float.jl b/base/float.jl index d5280ef74fbce..fc4cef09b48ad 100644 --- a/base/float.jl +++ b/base/float.jl @@ -688,22 +688,24 @@ function hash(x::Real, h::UInt) den_z = trailing_zeros(den) den >>= den_z pow += num_z - den_z - - # handle values representable as Int64, UInt64, Float64 + # If the real can be represented as an Int64, UInt64, or Float64, hash as those types. + # To be an Integer the denominator must be 1 and the power must be non-negative. if den == 1 + # left = ceil(log2(num*2^pow)) left = top_set_bit(abs(num)) + pow - right = pow + den_z - if -1074 <= right - if 0 <= right + # 2^-1074 is the minimum Float64 so if the power is smaller, not a Float64 + if -1074 <= pow + if 0 <= pow # if pow is non-negative, it is an integer left <= 63 && return hash(Int64(num) << Int(pow), h) left <= 64 && !signbit(num) && return hash(UInt64(num) << Int(pow), h) end # typemin(Int64) handled by Float64 case - left <= 1024 && left - right <= 53 && return hash(ldexp(Float64(num), pow), h) + # 2^1024 is the maximum Float64 so if the power is greater, not a Float64 + # Float64s only have 53 mantisa bits (including implicit bit) + left <= 1024 && left - pow <= 53 && return hash(ldexp(Float64(num), pow), h) end else h = hash_integer(den, h) end - # handle generic rational values h = hash_integer(pow, h) h = hash_integer(num, h) diff --git a/base/rational.jl b/base/rational.jl index baca2397c42ff..5b9ff99ea7a6c 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -549,9 +549,10 @@ function hash(x::Rational{<:BitInteger64}, h::UInt) num, den = Base.numerator(x), Base.denominator(x) den == 1 && return hash(num, h) den == 0 && return hash(ifelse(num > 0, Inf, -Inf), h) - if isodd(den) + if isodd(den) # since den != 1, this rational can't be a Float64 pow = trailing_zeros(num) num >>= pow + h = hash_integer(den, h) else pow = trailing_zeros(den) den >>= pow diff --git a/test/hashing.jl b/test/hashing.jl index 1c7c37d00f93b..8146edafea130 100644 --- a/test/hashing.jl +++ b/test/hashing.jl @@ -310,3 +310,18 @@ struct AUnionParam{T<:Union{Nothing,Float32,Float64}} end @test Type{AUnionParam{<:Union{Nothing,Float32,Float64}}} === Type{AUnionParam} @test Type{AUnionParam.body}.hash == 0 @test Type{Base.Broadcast.Broadcasted}.hash != 0 + + +@testset "issue 50628" begin + # test hashing of rationals that equal floats are equal to the float hash + @test hash(5//2) == hash(big(5)//2) == hash(2.5) + # test hashing of rational that are integers hash to the integer + @test hash(Int64(5)^25) == hash(big(5)^25) == hash(Int64(5)^25//1) == hash(big(5)^25//1) + # test integer/rational that don't fit in Float64 don't hash as Float64 + @test hash(Int64(5)^25) != hash(5.0^25) + @test hash((Int64(5)//2)^25) == hash(big(5//2)^25) + # test integer/rational that don't fit in Float64 don't hash as Float64 + @test hash((Int64(5)//2)^25) != hash(2.5^25) + # test hashing of rational with odd denominator + @test hash(5//3) == hash(big(5)//3) +end