string.format 四舍五入不可控,因依赖 ieee 754 浮点表示,2.035 可能显示为 "2.03";高精度场景必须用 bigdecimal(字符串构造)配合 setscale 指定舍入模式。

String.format 会四舍五入但不控精度来源
用 String.format 截小数,表面看最简单,比如 String.format("%.2f", 3.14159) 得到 "3.14"。但它底层调用的是 Double.toString 的舍入逻辑,依赖 IEEE 754 浮点表示——这意味着输入本身可能就不精确。
常见错误现象:String.format("%.2f", 2.035) 有时返回 "2.03" 而不是 "2.04",因为 2.035 在二进制中无法精确表达,实际存储值略小于 2.035,舍入时向下取了。
- 只适合对精度要求不严的展示场景(如日志、前端临时显示)
- 参数
"%.2f"中的2是显示位数,不是计算精度,不改变原始 double 值的误差 - 在金融、计费等场景下绝对不要用
BigDecimal.setScale 是唯一可控的截位方式
真正要“按指定小数位数、指定舍入模式”做截位,必须用 BigDecimal。它把数字转为十进制精确表示,再执行舍入,行为完全可预测。
关键点:不能直接用 new BigDecimal(double) 构造,否则浮点误差会带进来。必须用字符串构造。
立即学习“Java免费学习笔记(深入)”;
- 正确写法:
new BigDecimal("2.035").setScale(2, RoundingMode.HALF_UP) - 错误写法:
new BigDecimal(2.035).setScale(2, RoundingMode.HALF_UP)—— 此时传入的2.035已是近似值 -
setScale第二个参数必须显式指定,如RoundingMode.HALF_UP(常用),不能省略或用整数常量 - 性能上比
String.format略低,但换来的是确定性,值得
为什么不用 Math.round 配合乘除
有人写 (long)(x * 100 + 0.5) / 100.0 模拟四舍五入,这在多数情况下“看起来对”,但问题隐蔽。
典型翻车点:Math.round(0.145 * 100) 可能得 14 而非 15,因为 0.145 * 100 实际算出来是 14.499999999999998,Math.round 向下取整了。
- 所有基于
double运算再舍入的套路,都逃不开浮点表示缺陷 - 乘除过程会放大误差,尤其在边界值(如 .xxx5)附近极不稳定
- 代码可读性差,后续维护者容易误以为“只是数学运算”,忽略精度风险
实际项目里怎么选
看输入源头和业务敏感度。如果上游给的就是 String 或 int/long(比如数据库查出的金额字段是 DECIMAL 映射为字符串),无脑走 BigDecimal;如果只是临时打日志看个大概,String.format 更轻量。
- Web API 入参如果是 JSON 数字,Jackson 默认解析成
Double,此时要先转成字符串再喂给BigDecimal,或者改用@JsonFormat(using = ...)自定义反序列化 - MyBatis 查询
DECIMAL字段,配置jdbcType=DECIMAL并设resultType="java.math.BigDecimal",可避免中间转 double - 别为了“省几行代码”在关键路径上妥协——一次精度错误引发的对账差异,远比多写三行
BigDecimal代价大
真正麻烦的从来不是写哪几行,而是谁在哪个环节悄悄把 double 当成了精确数。










