Notes of go stepping on the pit (19)

Time:2020-12-4

What does the following code output?

func main() {
    m := make(map[float64]int)
    M [1.6] = 1 // 10 lines
    M[ math.NaN ()] = 2 // 11 lines

    fmt.Println(m[1.6])
    fmt.Println(m[1.60000001])
    fmt.Println(m[1.60000000000000001])
    fmt.Println(m[math.NaN()])
}

Output: 1 0 10

There are two pits in it

First, whym[1.60000000000000001]You can get the value

The second whym[math.NaN()]Cannot get value. Let’s analyze them one by one

First

Why?m[1.60000000000000001]You can get the value,
As usual, indecisive, assembly mechanics~

go tool compile -S main.go | grep main.go:10

Output results (I have removed both the beginning and the end, and only show the key points)

// ....
LEAQ    ""..stmp_0(SB), DX
PCDATA  $0, $0
MOVQ    DX, 16(SP)
CALL    runtime.mapassign(SB)
// ....

You can see that when the key of the map isfloat64In the case of type, thefloat64Type touin64

Specifically throughmath.Float64bits()Function completed

// 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)) }

Let’s test it

fmt.Println(math.Float64bits(1.6))
fmt.Println(math.Float64bits(1.60000000000000001))

output

4609884578576439706
4609884578576439706

As you can see,1.6and1.60000000000000001Finally, it is converted to Uint64. The result is the same, so we use them[1.60000000000000001]You can get the value, of course.

the second

m[math.NaN()]Why can’t we get the value? Let’s take a look first math.NaN () function details

// NaN returns an IEEE 754 ``not-a-number'' value.
func NaN() float64 { return Float64frombits(uvnan) }

Where uvnan is a constant

uvnan = 0x7FF8000000000001

Nan() calls directlyFloat64frombits, pass in the write dead value to the Nan type value. Since Nan is parsed from a constant, why is it considered as a different key when inserting a map? In fact, this is determined by the hash function of the type. For the float64 type, its hash function is as follows:

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)
    }
}

The second case: F! = f is for Nan, and a random number will be added here.
At this point, it is clear that the key of the map actually stores the hash of the value, and math.NAN () the hash values of return values are different, som[math.NaN()]There is no value.

Pay attention to my official account and learn GO language together

Notes of go stepping on the pit (19)

This work adoptsCC agreementThe author and the link to this article must be indicated in the reprint

Recommended Today

Api: tiktok: user video list

Tiktok tiktok, tiktok, tiktok, Api, Api, jitter, voice, bullet screen comments, jitter, jitter, and jitter. Tiktok tiktok tiktok data, jitter data acquisition, live broadcast of shaking sound Titodata: professional short video data acquisition and processing platform. For more information, please contact:TiToData Massive data collection Collect 500 million pieces of data for customers every day Tiktok […]