应使用string字段接收json数字并手动转decimal.decimal,避免float64精度丢失;因gin默认json.unmarshal将"19.99"解析为19.990000000000002,必须绕过float64中间环节。

Gin 默认用 json.Unmarshal 解析 JSON,而 Go 标准库的 json 包对浮点数采用 float64 存储,**无法精确表示 decimal 类型(如金融场景的 0.1 + 0.2 ≠ 0.3)**。这不是 Gin 的 bug,是底层 JSON 规范和 Go 类型系统的限制。
ShouldBindJSON 会把 "0.1" 解析成不精确的 float64
当你用 ShouldBindJSON 绑定含小数的字段时,Gin 不做额外处理,直接交给标准库。比如:
type Order struct {
Amount float64 `json:"amount" binding:"required"`
}
传 {"amount": 19.99} 进来,Amount 字段实际存的是 19.990000000000002 —— 这在金额校验、数据库写入或与外部系统比对时可能出问题。
- Go 的
float64遵循 IEEE-754,无法精确表示大多数十进制小数 -
binding:"required"等校验只检查是否为零值(0.0),不解决精度丢失 -
ShouldBindQuery或ShouldBindForm同样走字符串转float64,问题一致
用 string + 自定义 UnmarshalJSON 替代 float64 字段
最稳妥的做法:**把金额类字段声明为 string,再手动解析为高精度类型(如 github.com/shopspring/decimal.Decimal)**。Gin 允许你为结构体实现 UnmarshalJSON 方法,接管解析逻辑:
import "github.com/shopspring/decimal"
type Order struct {
AmountStr string `json:"amount"`
Amount decimal.Decimal `json:"-"` // 不参与 JSON 直接绑定
}
func (o *Order) UnmarshalJSON(data []byte) error {
type Alias Order // 防止无限递归
aux := &struct {
AmountStr string `json:"amount"`
*Alias
}{
Alias: (*Alias)(o),
}
if err := json.Unmarshal(data, aux); err != nil {
return err
}
o.Amount = decimal.NewFromFloat(0)
if aux.AmountStr != "" {
d, err := decimal.NewFromString(aux.AmountStr)
if err != nil {
return err
}
o.Amount = d
}
return nil
}
- 前端仍传
{"amount": "19.99"}(注意是字符串,不是数字) - 避免了 JSON 数字 → float64 → 精度污染的链路
-
decimal.Decimal内部用整数+缩放因子表示,可精确运算 - 别忘了在
ShouldBindJSON后检查err,它会捕获decimal.NewFromString的错误
不要依赖 binding 标签做精度校验
binding:"min=0.01" 这类标签对 float64 字段无效——因为 min 比较的是近似值,且标签本身不支持 decimal 类型。例如:
// ❌ 错误:binding 校验在 float64 上运行,精度已失
Amount float64 `json:"amount" binding:"required,min=0.01"`
// ✅ 正确:在 UnmarshalJSON 里或业务逻辑中做 decimal 级校验
if o.Amount.LessThan(decimal.NewFromFloat(0.01)) {
return errors.New("amount must be >= 0.01")
}
- Gin 的 binding 校验器只支持基础类型(
int,float64,string等),不识别第三方类型 - 即使你用
decimal.Decimal字段,也得自己写校验逻辑,不能塞进binding:标签 - 若必须用数字格式传参,至少要求前端传字符串,并在后端强制转换
真正要解决高精度浮点数,核心不是选哪个 Gin 方法,而是绕开 float64 这个坑。字符串输入 + 显式 decimal 解析,才是生产环境唯一可靠路径。中间任何环节用了 float64,精度就再也找不回来了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











