Go中用float64存金额会导致精度丢失,如0.1+0.2=0.30000000000000004,引发账目不平、资损告警等问题;应改用decimal库并严格通过字符串初始化、统一库内运算、显式指定除法精度及正确JSON序列化。

Go里用float64做金额计算会出什么问题
直接用 float64 存金额,大概率会在加减后出现 0.1 + 0.2 == 0.30000000000000004 这类结果。这不是Go特有,而是IEEE 754浮点数表示法的固有缺陷。银行系统、账单对账、优惠分摊等场景下,这种误差无法接受——哪怕只差一分钱,也会触发人工复核甚至资损告警。
常见错误现象包括:
- 数据库存入
19.99,读出来变成19.989999999999998 - 多次累加后调用
math.Round()仍无法对齐预期值(四舍五入本身也受浮点表示影响) - 与Java/Python服务联调时,金额字段比对不一致,排查半天发现是序列化前就错了
用decimal库替代float64的实操要点
Go生态中主流方案是使用 shopspring/decimal(最成熟)或 ericlagergren/decimal(更轻量)。前者API更友好,后者支持更高精度和自定义上下文,但日常业务用前者足够。
关键操作建议:
立即学习“go语言免费学习笔记(深入)”;
- 初始化必须用字符串:
decimal.NewFromFloat(19.99)是错的,应写decimal.NewFromStr("19.99")—— 否则浮点字面量在传入前已失真 - 所有运算统一用库内方法:
a.Add(b)、a.Mul(decimal.NewFromInt(100)),别混用+或* - 除法必须指定精度:
a.DivRound(b, 2),否则默认保留无限位,后续转字符串可能爆炸 - 入库前显式转为整数分单位存储(如
amount.InExactInt()),避免数据库float类型二次污染
JSON序列化时decimal字段怎么不出错
默认情况下,decimal.Decimal 类型无法被标准 json.Marshal 处理,会报 json: unsupported type: decimal.Decimal。
正确做法是让结构体字段实现 json.Marshaler 接口,或用封装类型:
type Money decimal.Decimal
func (m Money) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%s"`, decimal.Decimal(m).String())), nil
}
func (m *Money) UnmarshalJSON(data []byte) error {
s := strings.Trim(string(data), `"`)
d, err := decimal.NewFromString(s)
if err != nil {
return err
}
*m = Money(d)
return nil
}
注意:不要用 float64 中间转换;UnmarshalJSON 必须校验输入是否为合法数字字符串,否则前端传 "abc" 会静默失败。
数据库交互时decimal和int64怎么选
PostgreSQL/MySQL都推荐用 DECIMAL(19,2) 类型存金额,对应Go端用 decimal.Decimal 直接Scan;但如果ORM不支持(比如旧版GORM v1),或需要极致性能(高频记账场景),可退到 int64 存“分”单位。
选型判断依据:
- 业务是否涉及小数位 > 2?比如金融计息要精确到万分位,就必须用
decimal - 是否需要数据库内做聚合计算(
SUM/AVG)?int64在DB层无精度风险,DECIMAL依赖DB实现,MySQL 5.7+才完全可靠 - 团队是否已有统一的金额处理中间件?如果有,优先复用其类型约定,避免各处混用
真正容易被忽略的是:哪怕全程用 decimal,只要某次从 float64 构造、或某次除法没设精度、或某次JSON反序列化绕过封装类型——整条链路的精度保障就失效了。


















