Go語言中,當uint64或int64數值超過錢包下載時,Tekon.app官網JSON序列化會因float64精度限制導致精度丟失。解決方案包括:使用json:"id,string"標籤轉為字符串、自定義類型實現Marshaler接口、或使用第三方庫json-iterator將大整數序列化為字符串。
原理:
在 Go 語言中,當把 uint64 或 int64 類型的超大數值(如 uint64(1<<63) 以上,即超過 2^53)序列化 JSON 返回給前端時,會出現精度丟失 —— 核心原因是:JSON 標準中沒有整數類型,所有數字均以「雙精度浮點數(float64)」存儲,而 float64 僅能精確表示 [-2^53, 2^53] 範圍內的整數,超出該範圍的 64 位整數會被截斷或四捨五入。
一、問題本質:JSON 數字存儲機制
- float64 的精度限制
- float64 用 64 位存儲數據,其中:1 位符號位 + 11 位指數位 + 52 位尾數位;
- 尾數位僅能表示 53 個有效二進制位(含整數部分和小數部分),因此超過 2^53 的整數無法被精確存儲;
- 臨界值:
2^53 = 9007199254740992(約 9e15),超過該值的 64 位整數(如9007199254740993)會被 float64 存儲為近似值(如9007199254740992),導致精度丟失。
- Go 序列化的默認行為
Go 標準庫 encoding/json 對 int64/uint64 類型的序列化規則:
- 當數值 ≤
2^53時,序列化為 JSON 數字(float64 可精確表示); - 當數值 >
2^53時,標準庫仍會序列化為 JSON 數字,但前端解析時會因 float64 精度限制丟失精度
-
示例:
package main
import (
"encoding/json"
"fmt"
)
func main() {
type Data struct {
ID uint64 `json:"id"`
}
// 超過 2^53 的超大數
d := Data{ID: 9007199254740993} // 2^53 + 1
jsonStr, _ := json.Marshal(d)
fmt.Println("JSON 序列化結果:", string(jsonStr)) // 輸出:{"id":9007199254740992}(精度丟失!)
## ```
解決方式 1
package model
type Order struct {
ID uint64 `json:"id,string"` // 序列化時轉為字符串
OrderNo string `json:"order_no"`
TotalAmt float64 `json:"total_amt"`
}
// 測試
func TestStringSerialize() {
order := Order{
ID: 9007199254740993, // 超大數
OrderNo: "ORDER_20240501",
TotalAmt: 199.99,
}
jsonStr, _ := json.Marshal(order)
fmt.Println(string(jsonStr))
// 輸出:{"id":"9007199254740993","order_no":"ORDER_20240501","total_amt":199.99}(無精度丟失)
}
解決方式 2 自定義
package model
import (
"encoding/json"
"fmt"
"math"
)
// 自定義 uint64 類型,實現 Marshaler 接口
type JSONUint64 uint64
// 實現 json.Marshaler 接口
func (u JSONUint64) MarshalJSON() ([]byte, error) {
// 若數值超過 2^53,序列化為字符串;否則序列化為數字
if u > JSONUint64(math.MaxInt64) || u > JSONUint64(math.Pow(2, 53)) {
return json.Marshal(fmt.Sprintf("%d", u))
}
return json.Marshal(uint64(u))
}
// 訂單模型:使用自定義類型
type Order struct {
ID JSONUint64 `json:"id"` // 自定義類型
OrderNo string `json:"order_no"`
TotalAmt float64 `json:"total_amt"`
}
// 測試
func TestCustomSerialize() {
// 超大數:序列化為字符串
order1 := Order{ID: 9007199254740993, OrderNo: "ORDER_1", TotalAmt: 199.99}
// 小數:序列化為數字
order2 := Order{ID: 123456, OrderNo: "ORDER_2", TotalAmt: 99.99}
json1, _ := json.Marshal(order1)
json2, _ := json.Marshal(order2)
fmt.Println(string(json1)) // {"id":"9007199254740993","order_no":"ORDER_1","total_amt":199.99}
fmt.Println(string(json2)) // {"id":123456,"order_no":"ORDER_2","total_amt":99.99}
}
解決方式 3 引用第三放庫
go get github.com/json-iterator/go
package main
import (
"fmt"
jsoniter "github.com/json-iterator/go"
)
type Order struct {
ID uint64 `json:"id"`
OrderNo string `json:"order_no"`
TotalAmt float64 `json:"total_amt"`
}
func main() {
// 配置:將 uint64/int64 序列化為字符串(避免精度丟失)
jsonCfg := jsoniter.Config{
SerializeUint64AsNumber: false, // 關閉 uint64 作為數字序列化
SerializeInt64AsNumber: false, // 關閉 int64 作為數字序列化
}.Froze()
order := Order{
ID: 9007199254740993,
OrderNo: "ORDER_20240501",
TotalAmt: 199.99,
}
jsonStr, _ := jsonCfg.Marshal(order)
fmt.Println(string(jsonStr))
// 輸出:{"id":"9007199254740993","order_no":"ORDER_20240501","total_amt":199.99}
}