Go中float64乘除法不保留精度,需用math.Round缩放法或shopspring/decimal库实现精确控制;fmt.Sprintf格式化仅影响显示,无法修复底层浮点误差。

Go 里 float64 乘除法默认不保留精度,必须手动处理
Go 原生没有内置的“精度控制”算术类型,float64 运算结果直接走 IEEE 754,比如 1.01 * 100 得到 100.99999999999999 而非 101.0。这不是 bug,是浮点数本质限制。真要控制小数位数(比如财务计算),不能靠 float64 直接运算后格式化输出——那只是显示问题,底层值已失真。
用 math.Round + 缩放实现可控精度乘除
最轻量、无依赖的方案:先放大 → 四舍五入 → 再缩小。适用于整数位不多、小数位固定(如 2 位)的场景。
关键点:
-
math.Round对float64有效,但注意它返回float64,不是整数 - 缩放倍数必须是
float64类型,否则整数除法会截断(比如100要写成100.0) - 不要用
int(x*100 + 0.5)这类整数强转,负数时会出错(math.Round正确处理负数)
示例:保留 2 位小数的乘法
import "math"
<p>func roundTo2(x float64) float64 {
return math.Round(x*100) / 100.0
}</p><p>result := roundTo2(1.01 * 100) // 得到 101.0,不是 100.99999999999999
</p>
高精度需求必须用 shopspring/decimal 库
涉及金额、税率、科学计算等不允许任何累积误差的场景,float64 再怎么 round 都不可靠。此时应切换到定点数库,shopspring/decimal 是 Go 生态事实标准。
注意点:
- 所有输入必须显式转成
decimal.Decimal,比如decimal.NewFromFloat(1.01),不能直接传float64字面量(因浮点字面量本身已有误差) - 乘除法用
.Mul()/.Div()方法,它们接受decimal.Decimal参数,支持指定精度(.Mul(d, 2)表示结果保留 2 位小数) - 库默认使用 28 位精度,但除法需显式设精度,否则可能无限循环小数溢出
示例:精确的 2 位小数除法
import "github.com/shopspring/decimal" <p>a := decimal.NewFromFloat(101.0) b := decimal.NewFromFloat(3.0) // 保留 2 位小数,四舍五入 result := a.Div(b).Round(2) // 得到 "33.67" </p>
别在 fmt.Sprintf 里“假装”有精度
常见错误:用 fmt.Sprintf("%.2f", 1.01*100) 得到 "101.00" 就以为精度安全了。其实底层值仍是 100.99999999999999,后续再参与运算或比较(比如 == 101.0)就会失败。
正确做法只有一条:精度控制必须发生在数值计算链的**中间或上游**,而不是最后一步格式化。
容易被忽略的点:数据库读写时,如果字段是 DECIMAL,但 Go 端用 float64 接收,精度已在入库/出库时丢失——必须用 decimal.Decimal 或字符串方式交互。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











