必须用整数、decimal或math/big.rat代替float64存储金额,因其ieee 754二进制表示导致0.1+0.2≠0.3等精度丢失,引发对账错误;推荐优先使用整数法(如“分”为单位),其次github.com/shopspring/decimal(需避免newfromfloat64),最后math/big.rat。

别用 float64 存金额,这是金融计算出错的起点。所有后续四舍五入、比较、入库、对账问题,90% 都源于这一步。
为什么 float64 在金融场景里根本不能用
不是 Go 语言的问题,是 IEEE 754 二进制浮点数的固有缺陷:0.1 在内存里存的是 0.10000000000000000555...,0.2 同理。加完之后得到 0.30000000000000004,而不是 0.3。这种误差在单次运算中微小,但乘以千笔订单、叠加汇率换算、再做分润结算时,就会变成“对不上账”的真实资金差额。
常见错误现象包括:
-
0.1 + 0.2 == 0.3返回false -
math.Round(2.555 * 100) / 100得到2.55而非2.56 - 数据库查出
DECIMAL(10,2)字段,用Scan(&f float64)接收后,再转decimal.NewFromFloat64(f)—— 中间那层float64已经失真
三种可用方案,按推荐顺序排列
没有银弹,只有适配场景的选择:
-
整数法(最稳):金额统一转为最小单位(如“分”),用
int64存储和运算。例如19.99元 →1999分。所有加减乘除都无精度损失,落库类型为BIGINT或DECIMAL(19,0)。缺点是需手动处理单位转换,不适合需要动态小数位的场景(如期货合约报价) -
github.com/shopspring/decimal(最常用):十进制定点数,底层用整数模拟小数,支持自定义精度和舍入模式。关键要避开两个坑:
– 别用decimal.NewFromFloat64(19.99)初始化,改用decimal.NewFromString("19.99")或decimal.New(1999, 2);
– 所有中间计算尽量不Round(),只在最终输出或落库前做一次 -
math/big.Rat(最精确):以分数形式表示任意精度小数(如1/3),加减乘除全精确。但无法直接存入大多数数据库,需拆成Numerator和Denominator两个int64字段;且不支持直接格式化为固定小数位字符串,需调用FloatString(n),性能开销明显高于decimal
GORM 和数据库交互时的关键配置
即使用了 decimal.Decimal,如果 ORM 层没配对,照样掉坑里:
- GORM v2 必须写成:
Amount decimal.Decimal `gorm:"type:decimal(20,8);not null"`—— 类型和 tag 缺一不可 - 字段类型必须是
decimal.Decimal,不能是float64或string;否则 GORM 会退化为 float64 扫描 - PostgreSQL 用户更推荐用
pgx驱动 +pgtype.Numeric,比走interface{}再手动转更安全 - JSON 输出保持默认字符串格式(如
"19.99"),前端解析时用parseFloat或直接展示;强行改MarshalJSON输出数字,等于把精度黑洞又引回前端
真正容易被忽略的,不是“该用哪个库”,而是初始化那一刻:只要经过任何 float64 变量,精度就永久丢失了。从 HTTP body 解析、数据库 scan、甚至日志打印里的 fmt.Printf("%.2f", x),都可能是误差入口。守住字符串或整数源头,比后期补救重要十倍。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











