網(wǎng)站開發(fā) 實戰(zhàn)今日十大熱點新聞頭條
【Golang】Json 無法表示 float64 類型的 NaN 以及 Inf 導(dǎo)致的 panic
原因
golang 服務(wù)出現(xiàn)了 panic,根據(jù) panic 打印出的堆棧找到了問題代碼,看上去原因是:json 序列化時,遇到了無法序列化的內(nèi)容
[panic]: json: unsupported value: NaN or Infinite
NaN 以及 Infinite
解釋:基本可以判斷出:NaN 以及 Inf 是 float64 類型的兩種特例,Json 無法表示這類數(shù)據(jù),故 panic
深度剖析
查閱 log 看到,這里最原始的 NaN
其實是字符串"NaN"
,明明是字符串,是如何將 "NaN"
轉(zhuǎn)變?yōu)?float64 的呢?問題出在使用的 cast 包的 ToFloat64上
可以從 ToFloat64 的源碼中看到,當(dāng)需要轉(zhuǎn)換成 float64 的類型是 string 或者 json.Number 時,調(diào)用的都是 strconv.ParseFloat 函數(shù)(s.Float64 本質(zhì)也是調(diào)用該函數(shù)),繼續(xù)閱讀 strconv.ParseFloat,我們可以在strconv/atof.go文件中看到以下代碼:strconv.ParseFloat 會將字符串 NaN 以及 Inf 轉(zhuǎn)換為 float64
類型的 NaN 以及 Inf。 而 json 無法處理這兩種數(shù)據(jù),會直接 panic
修復(fù)
單獨判斷下即可
func SetValWhenFloatIsNaNOrInf(val float64) float64 {if math.IsNaN(val) {return 0.00}if math.IsInf(val, 0) {return 100.00}return val
}
擴(kuò)展
NaN 和 Inf 怎么來的呢
在 float64 類型中,我們可以通過 zero/zero 來得到 NaN,也可以用過 除零 操作來得到 Inf,在 Google 并沒有得到能解釋這兩種常量存在的原因,只從二進(jìn)制浮點數(shù)算術(shù)標(biāo)準(zhǔn)(IEEE 754)看到有相關(guān)的定義
能否把 NaN 以及 Inf 作為 map 的 key?
測試代碼
func TestNaNKeyMap() {m := make(map[float64]struct{}, 0)for i := 0; i < 10; i++ {m[math.NaN()] = struct{}{}fmt.Printf("nan map len:%d\n", len(m))}
}
func TestInfKeyMap() {m := make(map[float64]struct{}, 0)for i := 0; i < 10; i++ {m[math.Inf(0)] = struct{}{}fmt.Printf("inf map len:%d\n", len(m))}
}
結(jié)果:可以看待對于 NaN,每次賦值的時候,其實都是給不同的 key 賦值,而 Inf 則不是;所以我們可以得出以下結(jié)論:map[float64]struct 這種以 float64 為 key 的 map,存在內(nèi)存泄漏
的可能
map 的 key 都會經(jīng)過 hash,然后再確定value 存儲的位置,那么問題大概率出在 hash 算法上,在 runtime/alg.go 找到以下函數(shù):
可以看到,算法里判斷到 f != f
時,會給hash 值增加一個隨機數(shù),并且注釋里也說了是為了適配 any kind of NaN
這里 f != f
的判斷也同時用在 func IsNaN(f float64) (is bool)
函數(shù)中。