必须用 decimal.NewFromString 或 decimal.New 初始化金额,禁用 float64 和 decimal.NewFromFloat64;GORM 映射需字段类型与 type:decimal tag 同时正确;除法须显式控制 DivisionPrecision 或用 DivRound;JSON 序列化应保留字符串格式防 JS 解析失真。

别用 float64 存金额,也别用 decimal.NewFromFloat64() 初始化——这两步踩下去,后面所有对账、分润、幂等校验都会变成“找 bug 集训营”。
为什么必须绕开 float64 和 NewFromFloat64
根本原因不是 Go 语言的问题,而是 IEEE 754 二进制浮点数无法精确表示大多数十进制小数。比如 0.1 在内存里实际是 0.10000000000000000555,0.1 + 0.2 == 0.3 返回 false 是常态,不是异常。
更隐蔽的坑是:前端传 "19.99",你写 decimal.NewFromFloat64(19.99),函数内部先用 float64 解析字符串,失真已经发生,再转成 decimal.Decimal 只是把错误“包装得更体面”。
- 错误现象:
decimal.NewFromFloat64(2.555).Round(2).String()得到"2.55"(不是"2.56") - 正确初始化方式只有两种:
decimal.NewFromString("2.555")或decimal.New(2555, 3)(2555 × 10⁻³) -
NewFromString返回(decimal.Decimal, bool),生产环境必须检查ok,别依赖RequireFromString
GORM 映射 DECIMAL 字段必须类型和 tag 同时对齐
GORM v2 不认识 decimal.Decimal 类型,它只靠结构体字段类型 + struct tag 共同触发自动转换逻辑。缺一不可,否则退化为 float64 扫描,精度黑洞立刻生效。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 正确写法:
Amount decimal.Decimal `gorm:"type:decimal(20,8);not null"` - ❌ 错误写法:
Amount float64 `gorm:"type:decimal(20,8)"`(类型错,tag 白写) - ❌ 错误写法:
Amount decimal.Decimal `gorm:"column:amount"`(缺type:,GORM 不识别) - PostgreSQL 用户建议直接用
pgx+pgtype.Numeric,比走interface{}再手动转更安全
除法精度控制必须显式设置 DivisionPrecision
decimal.DivisionPrecision 默认是 16,但金融场景往往不需要这么高,反而容易掩盖问题。比如分摊 100 元给 3 人,100.Div(3) 默认输出 16 位小数,但业务要求保留 2 位,且必须按 RoundHalfUp 规则。
- 全局设置(启动时一次):
decimal.DivisionPrecision = 4(覆盖元/分场景) - 临时覆盖(单次运算):
a.DivRound(b, 2)比a.Div(b).Round(2)更可靠,避免中间结果多保留位数导致后续误差 - ⚠️
DivRound的第二参数是小数位数,不是总位数;Div则完全依赖全局DivisionPrecision - 别在中间链式运算中频繁
Round(),误差会叠加;只在最终落库或返回前做一次
JSON 序列化默认输出字符串是保护机制,别强行改
decimal.Decimal 的 MarshalJSON 默认输出带引号字符串(如 "19.99"),这是有意为之:防止前端 JS 用 JSON.parse 解析后变成 float64 再次失真。
- ✅ 前端统一按字符串接收,展示层再格式化(如 Intl.NumberFormat)
- ❌ 不要重写
MarshalJSON强行输出数字,除非你能保证下游所有消费者都用big.Float或等价高精度解析器 - ❌ 别用反射或全局替换
json.Number,嵌套结构里漏掉一个字段就全盘失效 - 如果真要输出数字,必须调用
d.Float64()并检查exact返回值,false就说明已超出float64表达范围,不能硬转
最易被忽略的不是“怎么算准”,而是“谁定义舍入规则”和“数据库字段精度是否匹配”。同一个 decimal(20,8) 字段,如果代码里用 DivisionPrecision=16 算完再截断存,和用 DivisionPrecision=8 直接算,入库值可能不同——这已经不是精度问题,是业务规则不一致。


















