数学工具
数学工具
shopspring/decimal 提供任意精度的十进制定点数,适合金额、税率等不能接受二进制浮点误差的场景。
go get github.com/shopspring/decimal
创建与运算
从外部读取小数时优先使用字符串,避免先转换为 float64 引入误差。
price, err := decimal.NewFromString("19.99")
if err != nil {
return err
}
quantity := decimal.NewFromInt(3)
rate := decimal.RequireFromString("0.08")
subtotal := price.Mul(quantity) // 59.97
tax := subtotal.Mul(rate) // 4.7976
total := subtotal.Add(tax) // 64.7676
payable := total.Round(2) // 64.77
Decimal 是不可变值,Add、Sub、Mul 和 Div 都返回新值,不会修改原对象。
比较与格式化
不要把小数转换为浮点数后比较:
balance := decimal.RequireFromString("100.00")
amount := decimal.RequireFromString("25.50")
if balance.GreaterThanOrEqual(amount) {
balance = balance.Sub(amount)
}
fmt.Println(balance.StringFixed(2)) // 74.50
常用比较方法包括 Equal、LessThan、LessThanOrEqual、GreaterThan 和 GreaterThanOrEqual。
除法与舍入
除法可能产生无限小数,应根据业务规则明确精度和舍入方式:
amount := decimal.RequireFromString("10.00")
average := amount.DivRound(decimal.NewFromInt(3), 2) // 3.33
bankers := amount.Div(decimal.NewFromInt(3)).RoundBank(2)
金额分摊时,三份 3.33 的总和只有 9.99。应把余数分配给其中一份,而不是分别舍入后忽略差额。
存储建议
- 数据库字段使用
DECIMAL或NUMERIC,不要使用浮点类型保存金额。 - API 中可将金额序列化为字符串,避免 JavaScript 客户端再次产生精度损失。
- 在业务边界统一精度和舍入规则,不要在展示层临时决定。
- 高频数值计算需要先做性能测试;不可变 API 会比原地修改的数值类型产生更多分配。