下面程式碼輸出什麼?
func main() {
m := make(map[float64]int)
m[1.6] = 1 // 10行
m[math.NaN()] = 2 // 11行
fmt.Println(m[1.6])
fmt.Println(m[1.60000001])
fmt.Println(m[1.60000000000000001])
fmt.Println(m[math.NaN()])
}
輸出:1 0 1 0
這裡面有個兩個坑點:
第一個 為什麼m[1.60000000000000001]
可以取到值
第二個 為什麼m[math.NaN()]
取不到值。我們一個一個來分析
首先第一個
為什麼 m[1.60000000000000001]
可以獲取到值,
老樣子,猶豫不決,彙編力學~
go tool compile -S main.go | grep main.go:10
輸出結果(首尾我都去掉了,只展示比較關鍵的地方)
// ....
LEAQ ""..stmp_0(SB), DX
PCDATA $0, $0
MOVQ DX, 16(SP)
CALL runtime.mapassign(SB)
// ....
可以看到當map的key是float64
型別的時候,會先將float64
型別轉成 uin64
具體是通過 math.Float64bits()
函式完成的
// Float64bits returns the IEEE 754 binary representation of f,
// with the sign bit of f and the result in the same bit position,
// and Float64bits(Float64frombits(x)) == x.
func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
我們測試一下
fmt.Println(math.Float64bits(1.6))
fmt.Println(math.Float64bits(1.60000000000000001))
輸出
4609884578576439706
4609884578576439706
可以看到,1.6
和 1.60000000000000001
最後轉成 uint64 型別結果是一樣的,所以我們使用 m[1.60000000000000001]
也就理所當然的可以獲取到值了。
第二個
m[math.NaN()]
為什麼取不到值,我們先看下 math.NaN() 函式具體內容
// NaN returns an IEEE 754 ``not-a-number'' value.
func NaN() float64 { return Float64frombits(uvnan) }
其中 uvnan 是一個常量
uvnan = 0x7FF8000000000001
NAN() 直接呼叫 Float64frombits
,傳入寫死的值得到 NAN 型值。既然,NAN 是從一個常量解析得來的,為什麼插入 map 時,會被認為是不同的 key?其實這是由型別的雜湊函式決定的,對於float64型別,它的雜湊函式如下:
func f64hash(p unsafe.Pointer, h uintptr) uintptr {
f := *(*float64)(p)
switch {
case f == 0:
return c1 * (c0 ^ h) // +0, -0
case f != f:
// any kind of NaN
return c1 * (c0 ^ h ^ uintptr(fastrand()))
default:
return memhash(p, h, 8)
}
}
第二個case: f != f 就是針對 NAN 的,這裡會再加一個隨機數。
到這裡就清楚了,因為 map 的 key 最後其實存的是值的hash,而math.NAN() 返回值的 hash 值都是不同的,所以 m[math.NaN()]
是取不到值的 。
關注我公眾號一起學習GO語言
本作品採用《CC 協議》,轉載必須註明作者和本文連結