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 中表示浮点数的原始类型有 float
和 double
:
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. 高级格式化用例
DecimalFormat
是 NumberFormat
的子类,支持自定义模式,适合复杂格式需求。
默认本地化格式
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
- 展示类格式化优先考虑
NumberFormat
和DecimalFormat
- 避免
Math.round
处理浮点数 - 注意
Locale
对格式的影响
所有示例代码已整理至 GitHub:https://github.com/baeldung/core-java-modules/tree/master/core-java-numbers-3