strconv.FormatFloat 是唯一能按需保留最少位数的方式:精度传正整数强制补零,传-1自动省略尾随零;math.Round需加偏移防浮点误差;math.Modf返回值存在显示失真;字符串切片提取小数位更高效可靠。

用 strconv.FormatFloat 控制小数位数再写入最稳
直接用 fmt.Fprintf 配合 %.2f 这类格式动词看似省事,但容易在边界值上翻车:比如 0.001 会被输出为 "0.00",12345678.0 用 %.2g 会变成 "1.2e+07"。而 strconv.FormatFloat 是唯一能真正“按需保留最少位数”的方式。
关键看第三个参数(精度):
- 传正整数(如
2)→ 强制保留该位并补零:strconv.FormatFloat(3.1, 'f', 2, 64)→"3.10" - 传
-1→ 自动省略尾随零,不四舍五入:strconv.FormatFloat(5.0, 'f', -1, 64)→"5",strconv.FormatFloat(3.1400, 'f', -1, 64)→"3.14" - 必须传
64作第四个参数(对应float64),传32会 panic
math.Round + 乘除法实现数值级四舍五入
如果目标是得到一个四舍五入后的 float64 值(而非字符串),不能依赖 int(x)——那是截断,不是四舍五入;也不能只用 math.Round(x * 100) / 100,因为 2.555 * 100 实际是 255.49999999999997,math.Round 后得 255,结果反而是 2.55。
更安全的做法是加一个微小偏移来对抗浮点误差:
立即学习“go语言免费学习笔记(深入)”;
func roundTo(x float64, precision int) float64 { shift := math.Pow(10, float64(precision)); return math.Round(x*shift+1e-10) / shift }- 适用于精度要求不极端的场景(如 UI 显示、日志统计)
- 若用于金融计算,此法仍不可靠,必须换用
github.com/shopspring/decimal
用 math.Modf 分离整数与小数部分时注意显示失真
math.Modf 是分离浮点数的标准方法,返回两个 float64:整数部分和小数部分。但它暴露的是底层 IEEE-754 表示值,不是你“看到”的十进制数。
例如 math.Modf(5.8) 返回的不是 5.0 和 0.8,而是 5.0 和 0.7999999999999998。这不是 bug,是二进制无法精确表示 0.8 的必然结果。
- 显示时永远用
fmt.Printf("%.4f", decPart)控制位数,别用fmt.Println(decPart) - 需要整数部分时,务必显式转成
int64:int64(intPartFloat),避免后续误当float64使用 - 负数行为符合数学定义:
math.Modf(-5.8)返回-5.0和-0.8,业务逻辑需确认是否接受
字符串切片提取小数位比正则快且可靠
如果你拿到的已经是字符串(比如从 JSON 或配置文件读出的 "12.3456789"),想取小数点后前 6 位数字(即 "345678"),别用正则——[!.]([\d]{1,6})$ 这类写法既错在转义(应为 \.),又错在锚点($ 匹配结尾,但小数位后面还有更多数字)。
标准库一行切片即可搞定:
- 先用
strings.Index(s, ".")找位置 - 然后
s[dotIndex+1 : min(dotIndex+1+places, len(s))],Go 1.21+ 可用内置min,否则手动判断越界 - 天然支持不足位自动截断(
"3.14"取 5 位 →"14"),也不怕"123"这种无小数点输入(返回空)
真正难的从来不是“怎么写”,而是决定要不要把浮点数当“精确值”用——只要涉及金额、计费、科学计算,float64 就不该出现在等号左边。


















