1. 简介
在开发中,我们经常需要对浮点数进行四舍五入处理——牺牲一定精度来换取更简洁、易读的结果。
本文将系统介绍 Kotlin 中几种常用的数值 rounding(四舍五入)方法,帮助你在不同场景下选择最合适的方式。避免踩坑 ❌,尤其是在金融计算或精度敏感的业务逻辑中。
2. 使用 BigDecimal 进行四舍五入 ✅
BigDecimal
是处理精确数值运算的首选类,尤其适用于金融、计费等对精度要求高的场景。它不仅支持任意精度,还能灵活指定小数位数和舍入模式(RoundingMode)。
基本用法
通过 setScale(scale, roundingMode)
方法可以设置保留几位小数,并指定舍入策略:
val rawPositive = 0.34444
val roundedUp = rawPositive.toBigDecimal().setScale(1, RoundingMode.UP).toDouble()
assertTrue(roundedUp == 0.4)
将 scale
改为 2,即可保留两位小数:
val roundedUp = rawPositive.toBigDecimal().setScale(2, RoundingMode.UP).toDouble()
assertTrue(roundedUp == 0.35)
常见 RoundingMode 对比
模式 | 行为说明 |
---|---|
RoundingMode.UP |
远离零方向进位(绝对值变大) |
RoundingMode.DOWN |
向零方向截断(绝对值不变或变小) |
RoundingMode.CEILING |
向正无穷方向进位 |
RoundingMode.FLOOR |
向负无穷方向进位 |
RoundingMode.HALF_UP |
四舍五入,最常见方式(学校教的) |
RoundingMode.HALF_DOWN |
五舍六入 |
RoundingMode.HALF_EVEN |
银行家舍入法:四舍六入,遇5看前一位奇偶 |
示例代码:
val rawPositive = 0.34444
val rawNegative = -0.3444
// DOWN: 向零截断
val roundedDown = rawPositive.toBigDecimal().setScale(1, RoundingMode.DOWN).toDouble()
assertTrue(roundedDown == 0.3)
val roundedDownNegative = rawNegative.toBigDecimal().setScale(1, RoundingMode.DOWN).toDouble()
assertTrue(roundedDownNegative == -0.3)
// CEILING: 向正无穷
val roundedCeiling = rawPositive.toBigDecimal().setScale(1, RoundingMode.CEILING).toDouble()
assertTrue(roundedCeiling == 0.4)
// FLOOR: 向负无穷
val roundedFloor = rawPositive.toBigDecimal().setScale(1, RoundingMode.FLOOR).toDouble()
assertTrue(roundedFloor == 0.3)
// HALF_UP: 四舍五入
val roundedHalfUp = 1.55.toBigDecimal().setScale(1, RoundingMode.HALF_UP).toDouble()
assertTrue(roundedHalfUp == 1.6)
// HALF_EVEN: 银行家舍入(避免统计偏差)
val roundedHalfEven = 1.55.toBigDecimal().setScale(1, RoundingMode.HALF_EVEN).toDouble()
assertTrue(roundedHalfEven == 1.6)
// HALF_DOWN: 五舍六入
val roundedHalfDown = 1.55.toBigDecimal().setScale(1, RoundingMode.HALF_DOWN).toDouble()
assertTrue(roundedHalfDown == 1.5)
⚠️ 注意:
HALF_EVEN
在处理大量数据时能减少累积误差,推荐用于财务系统。
3. 使用 String.format() 格式化四舍五入
String.format()
提供了一种轻量级的格式化方式,适合日志输出、界面展示等非精确计算场景。
语法如下:
val raw1 = 0.34
val raw2 = 0.35
val raw3 = 0.36
val rounded1: Double = String.format("%.1f", raw1).toDouble()
assertTrue(rounded1 == 0.3)
val rounded2: Double = String.format("%.1f", raw2).toDouble()
assertTrue(rounded2 == 0.4)
val rounded3: Double = String.format("%.1f", raw3).toDouble()
assertTrue(rounded3 == 0.4)
关键特性
- ✅ 默认行为等同于
RoundingMode.HALF_UP
- ❌ 无法自定义舍入模式
- ⚠️ 存在 Locale 相关陷阱!
Locale 踩坑警告 ❗
在非英语环境下(如法语 Locale.FRANCE
),小数点可能被格式化为逗号 ,
,导致后续 toDouble()
抛出 NumberFormatException
。
错误写法:
val rounded: Double = String.format("%.1f", raw1).toDouble() // 可能在 fr_FR 下报错
正确做法:显式指定 Locale:
val rounded: Double = String.format(Locale.ENGLISH, "%.1f", raw1).toDouble()
assertTrue(rounded == 0.3)
✅ 推荐:涉及字符串格式化的操作,务必指定
Locale.ENGLISH
或其他明确区域设置。
4. 使用 DecimalFormat 进行格式化
DecimalFormat
是 Java 中强大的数字格式化工具,Kotlin 中也可直接使用。相比 String.format()
,它提供了更多控制选项。
基本用法
val df = DecimalFormat("#.#", DecimalFormatSymbols(Locale.ENGLISH))
val raw1 = 0.34.toBigDecimal()
val raw2 = 0.35.toBigDecimal()
val raw3 = 0.36.toBigDecimal()
val rounded1 = df.format(raw1).toDouble()
assertTrue(rounded1 == 0.3)
val rounded2 = df.format(raw2).toDouble()
assertTrue(rounded2 == 0.4)
val rounded3 = df.format(raw3).toDouble()
assertTrue(rounded3 == 0.4)
📌 注意:
#.#
表示最多保留一位小数(不补零)- 默认舍入模式是
RoundingMode.HALF_EVEN
自定义舍入模式
可以通过 roundingMode
属性更改策略:
val df = DecimalFormat("#.#", DecimalFormatSymbols(Locale.ENGLISH))
df.roundingMode = RoundingMode.FLOOR // 向负无穷舍入
val rounded3Floor = df.format(raw3).toDouble()
assertTrue(rounded3Floor == 0.3)
更多格式符号
符号 | 含义 |
---|---|
# |
可选位数,不补零 |
0 |
必须位数,不够补零 |
. , , |
小数点和千分位分隔符(受 Locale 影响) |
例如:"0.00"
会强制保留两位小数,不足补零。
5. 总结
方法 | 优点 | 缺点 | 推荐场景 |
---|---|---|---|
BigDecimal.setScale() |
精确可控,支持多种舍入模式 | 略 verbose | ✅ 金融计算、高精度需求 |
String.format() |
简洁易用 | 不可定制舍入模式,Locale 敏感 | 🟡 日志/展示,注意 Locale |
DecimalFormat |
功能强大,可自定义格式与舍入 | API 稍复杂 | ✅ 复杂格式化需求 |
💡 最佳实践建议:
- 所有涉及金钱的计算,请使用
BigDecimal
+ 明确的RoundingMode
- 字符串格式化时,永远带上
Locale.ENGLISH
- 避免直接使用
Math.round()
或隐式转换,容易产生意料之外的结果
完整示例代码已上传至 GitHub:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-numbers