Gin中浮点数精度丢失是因json.Unmarshal默认将JSON数字转为float64,而float64无法精确表示超过2⁵³的整数及小数;正确做法是使用json.Decoder.UseNumber()获取原始字符串再按需转换。

前端传来的超大浮点数(比如 12345678901234567890.123)在 Gin 中解析后变成 12345678901234567000.0 ——这不是 Gin 的 bug,而是 Go 标准库 json.Unmarshal 默认把所有 JSON 数字转成 float64,而 float64 无法精确表示超过 2⁵³ 的整数部分,小数位也必然丢失。
为什么 Gin.BindJSON 会丢精度?
Gin 的 c.BindJSON() 底层调用的是 json.Unmarshal,它对 JSON 中的数字字段不做区分,一律解到 float64(即使结构体字段是 int64 或 string)。只要原始 JSON 里是数字字面量(如 "id": 12345678901234567890.123),Go 就先把它读成 float64,再尝试转目标类型——这一步已经不可逆地丢了精度。
- 哪怕你定义了
type Req struct { ID float64 `json:"id"` },输入"id": 12345678901234567890.123,ID的值也早已不是原样 -
json.Number是唯一能保真读取原始数字字符串的机制,但BindJSON不用它 - 前端发
{"price": 9999999999999999999.999},Gin 解出来可能是1e+19或四舍五入后的错值
正确做法:用 json.Decoder.UseNumber() 手动接管解析
必须绕过 c.BindJSON(),改用 json.NewDecoder 并开启 UseNumber(),才能拿到未失真的原始数字字符串。然后按需转 int64、string 或高精度类型。
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
- 在 handler 里先调
c.Request.Body,但注意 Body 只能读一次,若已读过(比如被中间件或日志消耗),需提前用c.Ctx.Request.Body = ioutil.NopCloser(bytes.NewReader(buf))复位 - 代码示例:
decoder := json.NewDecoder(c.Request.Body) decoder.UseNumber() var req map[string]interface{} if err := decoder.Decode(&req); err != nil { c.AbortWithStatusJSON(400, gin.H{"error": "invalid json"}) return } // 取 price 字段:先断言为 json.Number,再 .String() 或 .Int64() if priceNum, ok := req["price"].(json.Number); ok { priceStr := priceNum.String() // 完全保留原始字符串,包括小数位 // 后续可传给 big.Float 或 decimal 库处理 } - 别直接
req["price"].(float64),会 panic;也别int64(req["price"].(float64)),那只是对已失真值做转换
结构体绑定时如何避免?
如果你坚持用结构体绑定(比如 c.ShouldBindJSON(&req)),唯一可靠方式是把可能超大的数字字段声明为 json.Number 类型,而不是 float64 或 int64:
立即学习“前端免费学习笔记(深入)”;
- 定义:
type OrderReq struct { ID json.Number `json:"id"` Price json.Number `json:"price"` } - 后续使用:
idStr := req.ID.String()或idInt, _ := req.ID.Int64()(注意:仅当原始值是整数且 ≤ 2⁶³−1 时才安全) - 如果字段实际是小数(如价格),
Int64()会截断小数部分,必须用String()再喂给big.Float或decimal.Decimal - 别试图在结构体里写
Price string `json:"price"`并期望自动转换——JSON 解析器不会帮你把数字字面量转成字符串,除非你用了UseNumber()+ 手动处理
最易被忽略的一点:前端发的是数字还是字符串,决定了后端要不要做额外校验。如果后端要求价格必须是字符串格式("price": "9999999999999999999.999"),那 Gin 就完全不用碰 UseNumber(),但这就把格式责任推给了前端——而现实中,前端常直接写 fetch(url, { body: JSON.stringify({ price: xxx }) }),xxx 是 JS Number,一序列化就丢精度。所以真正可控的防线,永远在服务端解析环节。

















