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