
为什么 float64 算钱会出错
因为 float64 是二进制浮点数,无法精确表示很多十进制小数,比如 0.1 + 0.2 != 0.3。这不是 Go 特有,但 Go 默认用 float64 做数值计算,业务里一做金额加减就容易出现 0.19999999999999998 这种结果。
常见错误现象:fmt.Printf("%.2f", 19.99+0.01) 输出 20.00 看似正常,但底层值已失真;一旦参与比较(==)、数据库写入、或多次累加,误差会放大。
- 别在金融、计费、库存扣减等场景用
float64存金额 - 前端传来的
"19.99"字符串,别直接strconv.ParseFloat—— 一解析就丢精度 - 数据库字段如果是
DECIMAL,Go 层用float64接,等于主动把精度拱手让人
shopspring/decimal 怎么初始化才安全
这个库是 Go 里最常用的定点数实现,核心是 decimal.Decimal 类型。它内部用整数 + 小数位数(scale)模拟十进制运算,不依赖浮点硬件。
关键点:所有构造必须从字符串或整数开始,避开 float64 中间态。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 正确:
decimal.NewFromFloat(19.99)看似用了 float,但它是「近似转」,仅适合非关键场景;更推荐decimal.RequireFromString("19.99") - ✅ 更稳:
decimal.New(1999, 2)—— 表示 1999 × 10⁻²,即 19.99,完全无转换损耗 - ❌ 危险:
decimal.NewFromFloat(0.1 + 0.2),加法已在 float 层出错,再包一层没用 - 注意
decimal.RequireFromString会 panic,生产建议用decimal.FromString并检查ok返回值
四则运算和比较的坑在哪
decimal.Decimal 的加减乘除不是操作符重载,得调方法;比较也不能直接用 == 或 。
常见错误现象:写了 a == b,永远 false;或 a.Add(b) 后发现 a 没变 —— 因为 Add 返回新实例,不修改原值。
- 加减:
a.Add(b)、a.Sub(b),返回新decimal.Decimal,原变量不变 - 乘除:
a.Mul(b)默认保留双方 scale 和,可能产生过多小数位;需用a.MulRound(b, 2)显式指定保留 2 位 - 比较:
a.Cmp(b) == 0判断相等,a.Cmp(b) > 0表示 a > b - JSON 序列化默认输出字符串(如
"19.99"),如果后端要数字,得配json.Number或自定义 marshaler
和数据库交互时怎么避免精度断层
PostgreSQL 的 DECIMAL、MySQL 的 DECIMAL 都能存准值,但 ORM 层常悄悄转成 float64。
以 gorm 为例,默认扫描 DECIMAL 字段到 float64 字段,精度就没了。
- 模型字段类型必须是
decimal.Decimal,不是float64或string - GORM v2 要注册自定义 Scanner/Valuer:
func (d *Decimal) Scan(value interface{}) error和func (d Decimal) Value() (driver.Value, error) - 用
pgx时,注册pgtype.Numeric对应类型,比用interface{}接更可控 - 日志打出来看值是否带
scale=2,而不是只盯字符串输出 —— 字符串格式化可能掩盖内部 scale 错误
最麻烦的其实是上下游系统:支付网关返回的金额是字符串,你却用 float64 接;或者前端把 19.99 拼成 19.989999999999998 发过来 —— 这时候光靠库没用,得在边界做校验和清洗。










