1. 概述

本文将系统介绍 Java 中处理数字格式化的多种方式,并结合实际场景说明如何选择和使用。

数字格式化在日常开发中非常常见,比如金额展示、百分比、科学计数法、补零等。Java 提供了丰富的工具类来应对这些需求,合理使用能避免踩坑,提升代码健壮性。

2. 使用 String#format 进行基础格式化

String.format() 是最简单直接的格式化方式,适用于大多数基础场景。它支持类似 C 语言 printf 的格式化语法,灵活且无需引入额外对象。

double value = 4.2352989244d;
assertThat(String.format("%.2f", value)).isEqualTo("4.24");
assertThat(String.format("%.3f", value)).isEqualTo("4.235");

✅ 优点:简洁、无需额外依赖
❌ 缺点:仅生成字符串,不支持复杂本地化逻辑

⚠️ 注意:%.2f 表示保留两位小数并四舍五入(HALF_UP),这是最常见的舍入模式。

3. 小数位四舍五入处理

Java 中表示浮点数的原始类型有 floatdouble

double myDouble = 7.8723d;
float myFloat = 7.8723f;

但在实际业务中(如金融计算),我们通常只关心固定小数位。以下是几种常用四舍五入方法。

3.1 使用 BigDecimal 精确控制

BigDecimal 是处理精度要求高的首选方案,尤其适用于金额计算。

public static double withBigDecimal(double value, int places) {
    BigDecimal bigDecimal = new BigDecimal(value);
    bigDecimal = bigDecimal.setScale(places, RoundingMode.HALF_UP);
    return bigDecimal.doubleValue();
}

使用示例:

double D = 4.2352989244d;
assertThat(withBigDecimal(D, 2)).isEqualTo(4.24);
assertThat(withBigDecimal(D, 3)).isEqualTo(4.235);

✅ 推荐用于金融、支付等对精度敏感的场景
⚠️ 注意:构造 BigDecimal 时建议传入字符串,避免 double 精度问题(如 new BigDecimal("4.235")

3.2 使用 Math#round(不推荐)

通过乘以 10^n 再调用 Math.round() 实现小数位控制:

public static double withMathRound(double value, int places) {
    double scale = Math.pow(10, places);
    return Math.round(value * scale) / scale;
}

测试:

assertThat(withMathRound(D, 2)).isEqualTo(4.24);
assertThat(withMathRound(D, 3)).isEqualTo(4.235);

但该方法存在严重精度问题,不建议在生产环境使用

System.out.println(withMathRound(1000.0d, 17));
// 输出:92.23372036854776 !! 明显错误

System.out.println(withMathRound(260.775d, 2));
// 输出:260.77,期望是 260.78

❌ 原因:Math.round() 在浮点运算中会因精度丢失导致截断异常
✅ 结论:仅作学习参考,实际项目中应避免

4. 不同类型数字的格式化

针对特定场景(如货币、百分比),Java 提供了更专业的格式化工具。

4.1 大整数加千分位逗号

使用 DecimalFormat 配合模式 ###,###,### 可自动添加千分位分隔符:

public static String withLargeIntegers(double value) {
    DecimalFormat df = new DecimalFormat("###,###,###");
    return df.format(value);
}

int value = 123456789;
assertThat(withLargeIntegers(value)).isEqualTo("123,456,789");

✅ 适用于报表、金额展示等需要可读性的场景

4.2 数字前补零

使用 String.format 实现固定宽度补零:

public static String byPaddingZeros(int value, int paddingLength) {
    return String.format("%0" + paddingLength + "d", value);
}

int value = 1;
assertThat(byPaddingZeros(value, 3)).isEqualTo("001");

✅ 常用于生成编号、序列号、时间格式化(如 09:05:03

4.3 强制保留两位小数

使用 DecimalFormat 指定 .00 模式,确保输出始终有两位小数:

public static double withTwoDecimalPlaces(double value) {
    DecimalFormat df = new DecimalFormat("#.00");
    return Double.parseDouble(df.format(value));
}

int value = 12; 
assertThat(withTwoDecimalPlaces(value)).isEqualTo(12.00);

⚠️ 注意:返回的是 double,但值已按格式化后的字符串解析,精度可控

4.4 百分比格式化

使用 NumberFormat.getPercentInstance() 快速格式化百分比:

public static String forPercentages(double value, Locale locale) {
    NumberFormat nf = NumberFormat.getPercentInstance(locale);
    return nf.format(value);
}

double value = 25.0 / 100.0;
assertThat(forPercentages(value, Locale.US)).isEqualTo("25%");

✅ 支持国际化,不同 Locale 下格式可能不同(如德语中为 25 %

4.5 货币格式化

推荐使用 BigDecimal 存储金额,展示时用 NumberFormat.getCurrencyInstance()

public static String currencyWithChosenLocalisation(double value, Locale locale) {
    NumberFormat nf = NumberFormat.getCurrencyInstance(locale);
    return nf.format(value);
}

测试不同地区:

double value = 23_500;
assertThat(currencyWithChosenLocalisation(value, Locale.US)).isEqualTo("$23,500.00");
assertThat(currencyWithChosenLocalisation(value, Locale.CHINA)).isEqualTo("¥23,500.00");
assertThat(currencyWithChosenLocalisation(value, new Locale("pl", "PL"))).isEqualTo("23 500,00 zł");

✅ 自动适配货币符号、分隔符、小数点,国际化友好

4.6 科学计数法格式化

对于极大或极小数值,可用 String.format%E 格式:

public static String formatScientificNotation(double value, Locale locale) {
    return String.format(locale, "%.3E", value);
}

Locale us = Locale.US;
assertThat(formatScientificNotation(3.14159, us)).isEqualTo("3.142E+00");
assertThat(formatScientificNotation(0.0123456, us)).isEqualTo("1.235E-02");
assertThat(formatScientificNotation(1111111, us)).isEqualTo("1.111E+06");

还可指定最小宽度(不足补空格):

public static String formatScientificNotationWithMinChars(double value, Locale locale) {
    return String.format(locale, "%12.4E", value);
}

assertThat(formatScientificNotationWithMinChars(3.14159, us)).isEqualTo("  3.1416E+00");

✅ 适用于科学计算、日志输出等场景

5. 高级格式化用例

DecimalFormatNumberFormat 的子类,支持自定义模式,适合复杂格式需求。

默认本地化格式

public static String withDecimalFormatLocal(double value) {
    DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.getDefault());
    return df.format(value);
}

不同地区数字格式差异:

  • 美国:1,000.50
  • 德国:1.000,50
  • 印度:1,00,000(两位分组)

自定义模式控制小数位

public static String withDecimalFormatPattern(double value, int places) {
    DecimalFormat df2 = new DecimalFormat("#,###,###,##0.00");
    DecimalFormat df3 = new DecimalFormat("#,###,###,##0.000");
    if (places == 2) return df2.format(value);
    else if (places == 3) return df3.format(value);
    else throw new IllegalArgumentException("仅支持 2 或 3 位小数");
}

测试:

double D = 4.2352989244d;
assertThat(withDecimalFormatPattern(D, 2)).isEqualTo("4.24");
assertThat(withDecimalFormatPattern(D, 3)).isEqualTo("4.235");

⚠️ 注意:DecimalFormat 返回的是字符串,若需转回数字需用 parse(),注意异常处理

6. 总结

Java 提供了多层次的数字格式化能力:

方法 适用场景 是否推荐
String.format 简单格式化
BigDecimal.setScale 高精度计算 ✅✅✅
Math.round —— ❌(有精度陷阱)
DecimalFormat 自定义模式
NumberFormat.getCurrencyInstance 货币展示
NumberFormat.getPercentInstance 百分比

✅ 核心建议:

  • 金融计算务必用 BigDecimal
  • 展示类格式化优先考虑 NumberFormatDecimalFormat
  • 避免 Math.round 处理浮点数
  • 注意 Locale 对格式的影响

所有示例代码已整理至 GitHub:https://github.com/baeldung/core-java-modules/tree/master/core-java-numbers-3


原始标题:Number Formatting in Java | Baeldung