1. 概述

本文将介绍在Java中如何将数字精确保留到N位小数。我们将探讨多种实现方式,包括原生API和第三方库,并分析各自的优缺点。

2. Java中的小数类型

Java提供了两种基本类型存储小数:

  • float:单精度浮点数
  • double:双精度浮点数(默认类型)
double PI = 3.1415;

⚠️ 重要提醒:这两种类型都不适合存储精确值(如货币计算)。对于需要精确计算的场景,应使用BigDecimal类。

3. 格式化输出(不改变实际值)

如果只是需要格式化输出显示,而不改变实际数值:

3.1 使用printf格式化

System.out.printf("保留3位小数: %.3f %n", PI);
// 输出: 保留3位小数: 3.142

3.2 使用DecimalFormat

DecimalFormat df = new DecimalFormat("###.###");
System.out.println(df.format(PI));

DecimalFormat优势:

  • 可显式设置舍入行为
  • String.format()提供更精细的控制

4. 使用BigDecimal精确舍入

4.1 基础实现

private static double round(double value, int places) {
    if (places < 0) throw new IllegalArgumentException();

    BigDecimal bd = new BigDecimal(Double.toString(value));
    bd = bd.setScale(places, RoundingMode.HALF_UP);
    return bd.doubleValue();
}

⚠️ 关键踩坑点:必须使用BigDecimal(String)构造器!避免直接传入double值导致的精度问题。

4.2 使用Apache Commons Math库

添加依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-math3</artifactId>
    <version>3.5</version>
</dependency>

使用示例:

Precision.round(PI, 3);

✅ 特点:

  • 默认使用HALF_UP舍入模式
  • 可通过第三个参数自定义舍入行为

5. 使用DoubleRounder(decimal4j库)

添加依赖:

<dependency>
    <groupId>org.decimal4j</groupId>
    <artifactId>decimal4j</artifactId>
    <version>1.0.3</version>
</dependency>

使用示例:

DoubleRounder.round(PI, 3);

已知缺陷

System.out.println(DoubleRounder.round(256.025d, 2));
// 输出: 256.02 而非期望的 256.03

6. Math.round()方法(不推荐)

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

严重问题

System.out.println(roundAvoid(1000.0d, 17));
// 输出: 92.23372036854776 !!
System.out.println(roundAvoid(260.775d, 2));
// 输出: 260.77 而非期望的 260.78

⚠️ 结论:此方法存在截断误差,仅作学习参考,生产环境禁用!

7. 方案对比总结

方法 精确度 性能 推荐场景
BigDecimal ✅ 最高 中等 金融计算等高精度场景
Apache Commons Math ✅ 高 通用项目快速实现
DoubleRounder ❌ 存在缺陷 最高 非256.025类特殊值场景
Math.round() ❌ 错误率高 最高 仅限学习演示

8. 最佳实践建议

  1. 金融计算场景:必须使用BigDecimal,并始终使用字符串构造器
  2. 通用场景:推荐Apache Commons Math库
  3. 性能敏感场景:测试确认无特殊值后可考虑DoubleRounder
  4. 绝对避免:直接使用Math.round()进行小数位舍入

本文所有示例代码可在GitHub仓库获取。


原始标题:How to Round a Number to N Decimal Places in Java

« 上一篇: Java 9 响应式流详解
» 下一篇: Java周报,180