不能用float64做金额计算,因0.1等十进制小数在二进制中无限循环,float64截断存储导致误差从赋值起即固化,后续运算、舍入、比较均基于该误差,终致分角级对账失败。

用 float64 做货币结算,迟早出一分钱的差错——这不是 bug,是 IEEE 754 的必然结果。必须放弃浮点数,改用定点十进制或整数单位建模。
为什么不能用 float64 做金额计算
0.1 在二进制里是无限循环小数,float64 存储时强制截断,误差从赋值那一刻就已固化。后续加减乘除、四舍五入、比较,全是带着误差在算:
-
0.1 + 0.2 == 0.3返回false -
math.Round(2.555*100)/100得到2.55(不是2.56),因为2.555*100实际是255.49999999999997 - 多次累加后,误差可能放大到分甚至角的量级,对账直接失败
推荐方案:shopspring/decimal 库的正确初始化
第三方库 github.com/shopspring/decimal 是目前 Go 生态中金融结算事实标准。但初始化方式直接影响精度安全:
- ❌ 避免
decimal.NewFromFloat(19.99):传入的19.99本身已是float64近似值,误差已污染源头 - ✅ 推荐
decimal.RequireFromString("19.99"):字符串无转换失真,最安全 - ✅ 或
decimal.NewFromInt(1999).Div(decimal.NewFromInt(100)):全程整数运算,可控可验 - ⚠️ 注意:所有运算(
Add、Mul、Div)默认不自动缩放小数位,需显式调用Round或配置decimal.DivisionPrecision
GORM 模型与数据库字段如何对齐
应用层用 decimal.Decimal,数据库必须匹配 DECIMAL 类型,否则 GORM 扫描时会隐式转成 float64 再构造 Decimal,白忙一场:
- MySQL 表字段定义为
DECIMAL(20,2)(20 位总长,2 位小数) - GORM v2 模型字段标签写
gorm:"type:decimal(20,2);not null" -
decimal.Decimal类型自带driver.Valuer和sql.Scanner实现,无需额外注册 - 迁移时检查驱动是否支持:MySQL 原生支持;PostgreSQL 用
NUMERIC(20,2),语义等价
结算逻辑中容易被忽略的除法陷阱
均分 1 分钱(如 1 分三等分)是检验精度的“压力测试”。decimal 默认除法会截断或四舍五入,无法表达 1/3 分这种无限小数:
- 若业务允许近似(如按分四舍五入),用
DivRound并指定精度:a.DivRound(b, 2) - 若必须保留数学精确性(如清算、审计场景),应切换到
math/big.Rat:new(big.Rat).Quo(ratA, ratB),它以分子/分母形式保存,零丢失 -
big.Rat不适合高频读写,仅用于关键路径的最终拆分计算,结果仍建议转为字符串或整数存档
真正难的不是选哪个库,而是从第一行金额输入开始就杜绝 float64 浸入——API 入参校验、数据库迁移脚本、日志打印格式,全链条都要守住这个边界。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











