From 1903cf23c91454867c0c2260928736cda14aef3a Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Tue, 7 Dec 2021 19:03:18 -0800 Subject: [PATCH] src/runtime: improve float/complex hashing This allows positive and negative zero to hash to the same value, as required by Go. This is not perfect, but the best I could do without revamping all the hash funtions to take a seed. Fixes #2356 --- src/runtime/hashmap.go | 30 +++++++++++++++++++++++++-- testdata/map.go | 46 +++++++++++++++++++++++++++++++++++++++++- testdata/map.txt | 4 ++++ 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 826c0da3..07d46b57 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -356,6 +356,24 @@ func hashmapStringDelete(m *hashmap, key string) { //go:linkname valueInterfaceUnsafe reflect.valueInterfaceUnsafe func valueInterfaceUnsafe(v reflect.Value) interface{} +func hashmapFloat32Hash(ptr unsafe.Pointer) uint32 { + f := *(*uint32)(ptr) + if f == 0x80000000 { + // convert -0 to 0 for hashing + f = 0 + } + return hashmapHash(unsafe.Pointer(&f), 4) +} + +func hashmapFloat64Hash(ptr unsafe.Pointer) uint32 { + f := *(*uint64)(ptr) + if f == 0x8000000000000000 { + // convert -0 to 0 for hashing + f = 0 + } + return hashmapHash(unsafe.Pointer(&f), 8) +} + func hashmapInterfaceHash(itf interface{}) uint32 { x := reflect.ValueOf(itf) if x.RawType() == 0 { @@ -374,13 +392,21 @@ func hashmapInterfaceHash(itf interface{}) uint32 { return hashmapHash(ptr, x.RawType().Size()) case reflect.Bool, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return hashmapHash(ptr, x.RawType().Size()) - case reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: + case reflect.Float32: // It should be possible to just has the contents. However, NaN != NaN // so if you're using lots of NaNs as map keys (you shouldn't) then hash // time may become exponential. To fix that, it would be better to // return a random number instead: // https://research.swtch.com/randhash - return hashmapHash(ptr, x.RawType().Size()) + return hashmapFloat32Hash(ptr) + case reflect.Float64: + return hashmapFloat64Hash(ptr) + case reflect.Complex64: + rptr, iptr := ptr, unsafe.Pointer(uintptr(ptr)+4) + return hashmapFloat32Hash(rptr) ^ hashmapFloat32Hash(iptr) + case reflect.Complex128: + rptr, iptr := ptr, unsafe.Pointer(uintptr(ptr)+8) + return hashmapFloat64Hash(rptr) ^ hashmapFloat64Hash(iptr) case reflect.String: return hashmapStringHash(x.String()) case reflect.Chan, reflect.Ptr, reflect.UnsafePointer: diff --git a/testdata/map.go b/testdata/map.go index 558688dc..1d7cbf28 100644 --- a/testdata/map.go +++ b/testdata/map.go @@ -1,6 +1,8 @@ package main -import "sort" +import ( + "sort" +) var testmap1 = map[string]int{"data": 3} var testmap2 = map[string]int{ @@ -109,6 +111,48 @@ func main() { squares = make(map[int]int, 20) testBigMap(squares, 40) println("tested growing of a map") + + floatcmplx() +} + +func floatcmplx() { + + var zero float64 + var negz float64 = -zero + + // test that zero and negative zero hash to the same thing + m := make(map[float64]int) + m[zero]++ + m[negz]++ + println(m[negz]) + + cmap := make(map[complex128]int) + + var c complex128 + c = complex(zero, zero) + cmap[c]++ + + c = complex(negz, negz) + cmap[c]++ + + c = complex(0, 0) + println(cmap[c]) + + c = complex(1, negz) + cmap[c]++ + + c = complex(1, zero) + cmap[c]++ + + println(cmap[c]) + + c = complex(negz, 2) + cmap[c]++ + + c = complex(zero, 2) + cmap[c]++ + + println(cmap[c]) } func readMap(m map[string]int, key string) { diff --git a/testdata/map.txt b/testdata/map.txt index 30988108..d32dae55 100644 --- a/testdata/map.txt +++ b/testdata/map.txt @@ -72,3 +72,7 @@ structMap[{"tau", 3.14}]: 0 structMap[{"tau", 6.28}]: 0 tested preallocated map tested growing of a map +2 +2 +2 +2