Go踩坑笔记(十九)
下面代码输出什么?
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语言,1~18的坑都在我公众号上,有时间我再搬运过来吧
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: