JuliaPhysics / Measurements.jl

Error propagation calculator and library for physical measurements. It supports real and complex numbers with uncertainty, arbitrary precision calculations, operations with arrays, and numerical integration.

Home Page:https://juliaphysics.github.io/Measurements.jl/stable/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Error when hashing Measurement{Float64}

jwscook opened this issue · comments

Hello,

I've encountered this error:

julia> hash(1.0 ± 0.1)
ERROR: MethodError: no method matching decompose(::Measurement{Float64})
Closest candidates are:
  decompose(::Integer) at float.jl:555
  decompose(::Float16) at float.jl:557
  decompose(::Float32) at float.jl:568
  ...
Stacktrace:
 [1] hash(x::Measurement{Float64}, h::UInt64)
   @ Base ./float.jl:491
 [2] hash(x::Measurement{Float64})
   @ Base ./hashing.jl:18
 [3] top-level scope
   @ REPL[6]:1

What would the fix be? Declare hash by hand or implement decompose?

Something like:

Base.hash(m::Measurement) = foldr(hash, (m.val, m.err, m.der, m.tag))

or maybe if tag can be hijacked:

Base.hash(m::Measurement) = foldr(hash, (m.val, m.err, m.der); init=m.tag)

Versions:

 [eff96d63] Measurements v2.6.0
 Version 1.6.1-pre.39 (2021-04-17)

Frankly, I've never implemented hashing because

  1. I don't understand how it's supposed to work for non-standard numbers (and lack of documentation about this doesn't help much)
  2. so far I never had to implement them in practice.

I'm happy to do it if someone can help in particular with point 1, or if they open a pull request 🙂

Taking inspiration from other packages:

Out of curiosity, did you find a case in practice where the hash method was needed? I'm aware the method isn't implemented, but I couldn't find a real-world case in my applications where this would be needed.

One instance it using a Measurement as a key in a Dict:

julia> using Measurements

julia> m = 1.0 ± 0.1
1.0 ± 0.1

julia> Dict(m=>"anything else here")
ERROR: MethodError: no method matching decompose(::Measurement{Float64})
Closest candidates are:
  decompose(::Integer) at float.jl:555
  decompose(::Float16) at float.jl:557
  decompose(::Float32) at float.jl:568
  ...
Stacktrace:
 [1] hash(x::Measurement{Float64}, h::UInt64)
   @ Base ./float.jl:491
 [2] hash(x::Measurement{Float64})
   @ Base ./hashing.jl:18
 [3] hashindex(key::Measurement{Float64}, sz::Int64)
   @ Base ./dict.jl:169
 [4] ht_keyindex2!(h::Dict{Measurement{Float64}, String}, key::Measurement{Float64})
   @ Base ./dict.jl:310
 [5] setindex!(h::Dict{Measurement{Float64}, String}, v0::String, key::Measurement{Float64})
   @ Base ./dict.jl:383
 [6] Dict{Measurement{Float64}, String}(kv::Tuple{Pair{Measurement{Float64}, String}})
   @ Base ./dict.jl:104
 [7] Dict(ps::Pair{Measurement{Float64}, String})
   @ Base ./dict.jl:124
 [8] top-level scope
   @ REPL[3]:1

julia> Base.hash(m::Measurement) = foldr(hash, (m.val, m.err, m.der, m.tag))

julia> Dict(m=>"anything else here")
Dict{Measurement{Float64}, String} with 1 entry:
  1.0±0.1 => "anything else here"

I've added the hash method in #104. However, due to the fact isequal(x, y) must imply hash(x) == hash(y) we have this weird situation:

julia> x = 3 ± 0.1
3.0 ± 0.1

julia> a = 3 ± 0.1
3.0 ± 0.1

julia> x === a
false

julia> d = Dict(x => 42)
Dict{Measurement{Float64}, Int64} with 1 entry:
  3.0±0.1 => 42

julia> d[x]
42

julia> d[a]
42

julia> d[a] = 0
0

julia> d[a]
0

julia> d[x]
0

I don't think this can be avoided given the requirement on the relation between isequal and hash