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


原始标题:Rounding Numbers in Kotlin

» 下一篇: Kotlin 中的位运算