From 02a158f2ae4863645ce91d57364b0bcb97bf6010 Mon Sep 17 00:00:00 2001 From: oscarddssmith Date: Mon, 24 Jul 2023 10:55:03 -0400 Subject: [PATCH 1/8] fix hashing --- base/float.jl | 9 +++------ base/rational.jl | 3 ++- test/hashing.jl | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/base/float.jl b/base/float.jl index d5280ef74fbce..f17c57eea39e7 100644 --- a/base/float.jl +++ b/base/float.jl @@ -688,22 +688,19 @@ 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 den == 1 left = top_set_bit(abs(num)) + pow - right = pow + den_z - if -1074 <= right - if 0 <= right + if -1074 <= pow + if pow >= 0 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) + 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..5a8ece4b435d4 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(5^25) == hash(big(5)^25) == hash(5^25//1) == hash(big(5)^25//1) + # test integer/rational that don't fit in Float64 don't hash as Float64 + @test hash(5^25) != hash(5.0^25) + @test hash((5//2)^25) == hash(big(5//2)^25) + # test integer/rational that don't fit in Float64 don't hash as Float64 + @test hash((5//2)^25) != hash(2.5^25) + # test hashing of rational with odd denominator + @test hash(5//3) == hash(big(5)//3) +end From 0d4b2a1d61fe280a4fd98fbfbcf820e0d354d98a Mon Sep 17 00:00:00 2001 From: oscarddssmith Date: Mon, 24 Jul 2023 11:13:33 -0400 Subject: [PATCH 2/8] add comments --- base/float.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/base/float.jl b/base/float.jl index f17c57eea39e7..8d327bab0a17e 100644 --- a/base/float.jl +++ b/base/float.jl @@ -688,14 +688,20 @@ 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 is an Int64, UInt64, or Float64, hash as those types. + # To be an Integer the denominator must be 1 and the power must be positive. + # To be a Float64 if den == 1 + # left = ceil(log2(num)*2^pow) left = top_set_bit(abs(num)) + pow + # 2^-1074 is the minimum Float64 so if the power is smaller, not a Float64 if -1074 <= pow - if pow >= 0 + if pow >= 0 # if pow is negative, it isn't 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 + # 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 From 760c8bee0603f6c69442e5aab3b2aeefe2ccdc5f Mon Sep 17 00:00:00 2001 From: oscarddssmith Date: Mon, 24 Jul 2023 11:40:39 -0400 Subject: [PATCH 3/8] typo --- base/float.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/base/float.jl b/base/float.jl index 8d327bab0a17e..2956230f2ba1e 100644 --- a/base/float.jl +++ b/base/float.jl @@ -690,7 +690,6 @@ function hash(x::Real, h::UInt) pow += num_z - den_z # If the real is an Int64, UInt64, or Float64, hash as those types. # To be an Integer the denominator must be 1 and the power must be positive. - # To be a Float64 if den == 1 # left = ceil(log2(num)*2^pow) left = top_set_bit(abs(num)) + pow From eaffc71216a67d53006482bcc9c6ca600e51dbf0 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Mon, 24 Jul 2023 11:43:06 -0400 Subject: [PATCH 4/8] Update base/float.jl Co-authored-by: Lilith Orion Hafner --- base/float.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/float.jl b/base/float.jl index 2956230f2ba1e..1278ca63fd1ab 100644 --- a/base/float.jl +++ b/base/float.jl @@ -689,7 +689,7 @@ function hash(x::Real, h::UInt) den >>= den_z pow += num_z - den_z # If the real is an Int64, UInt64, or Float64, hash as those types. - # To be an Integer the denominator must be 1 and the power must be positive. + # 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 From 08fdf0a3eadfe04ba390b36052e041b6c30b1cdc Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Mon, 24 Jul 2023 11:43:13 -0400 Subject: [PATCH 5/8] Update base/float.jl Co-authored-by: Lilith Orion Hafner --- base/float.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/float.jl b/base/float.jl index 1278ca63fd1ab..b7f0b824b810d 100644 --- a/base/float.jl +++ b/base/float.jl @@ -691,7 +691,7 @@ function hash(x::Real, h::UInt) # If the real is 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 = ceil(log2(num*2^pow)) left = top_set_bit(abs(num)) + pow # 2^-1074 is the minimum Float64 so if the power is smaller, not a Float64 if -1074 <= pow From 0fcac7a703f8bd9bb70809943f1dd98a531e7ad0 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Mon, 24 Jul 2023 11:47:20 -0400 Subject: [PATCH 6/8] Update base/float.jl Co-authored-by: Lilith Orion Hafner --- base/float.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/float.jl b/base/float.jl index b7f0b824b810d..29e69d5a5c584 100644 --- a/base/float.jl +++ b/base/float.jl @@ -688,7 +688,7 @@ function hash(x::Real, h::UInt) den_z = trailing_zeros(den) den >>= den_z pow += num_z - den_z - # If the real is an Int64, UInt64, or Float64, hash as those types. + # 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)) From 1f2b5e02efe6787acc7e37b5ff2fde002967e9e6 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Mon, 24 Jul 2023 12:07:38 -0400 Subject: [PATCH 7/8] Update base/float.jl Co-authored-by: Lilith Orion Hafner --- base/float.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/float.jl b/base/float.jl index 29e69d5a5c584..fc4cef09b48ad 100644 --- a/base/float.jl +++ b/base/float.jl @@ -695,7 +695,7 @@ function hash(x::Real, h::UInt) left = top_set_bit(abs(num)) + pow # 2^-1074 is the minimum Float64 so if the power is smaller, not a Float64 if -1074 <= pow - if pow >= 0 # if pow is negative, it isn't an integer + 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 From a87b1649d1e251639f05436c47157b79aa1390a4 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Tue, 25 Jul 2023 21:48:37 -0400 Subject: [PATCH 8/8] fix 32 bit tests --- test/hashing.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/hashing.jl b/test/hashing.jl index 5a8ece4b435d4..8146edafea130 100644 --- a/test/hashing.jl +++ b/test/hashing.jl @@ -316,12 +316,12 @@ struct AUnionParam{T<:Union{Nothing,Float32,Float64}} end # 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(5^25) == hash(big(5)^25) == hash(5^25//1) == hash(big(5)^25//1) + @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(5^25) != hash(5.0^25) - @test hash((5//2)^25) == hash(big(5//2)^25) + @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((5//2)^25) != hash(2.5^25) + @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