1. 引言
在现实中,我们很少需要看到小数点后很长的“尾巴”。例如,计算商品价格、工资、平均分或房间面积时,通常只需要小数点后两位,而不是 15 位。
例如,除法的结果:
double x = 10.0 / 3.0;
System.out.println(x); // 3.3333333333333335
用户看到这样的结果可能会被吓到,以为程序出错了。因此,数字常常需要进行舍入——例如取最接近的整数、小数点后两位,等等。
在 Java 中有多种便捷的工具可以做到这一点。
2. 方法 Math.round():舍入到最接近的整数
最简单的方式是使用 Math.round() 方法。它接收一个浮点数(可以是 float,也可以是 double),并返回最接近的整数。
Math.round 的工作方式
- 如果小数部分小于 0.5,则向下取整。
- 如果小数部分大于等于 0.5,则向上取整。
示例:
System.out.println(Math.round(2.3)); // 2
System.out.println(Math.round(2.7)); // 3
System.out.println(Math.round(2.5)); // 3
System.out.println(Math.round(-2.5)); // -2
返回值类型
- 传入 float 时,返回 int。
- 传入 double 时,返回 long。
float f = 5.8f;
int roundedF = Math.round(f); // 6
double d = 5.8;
long roundedD = Math.round(d); // 6
注意:有时这会让人意外——对 double 进行了舍入,却得到的是 long!如果你需要 int,就要进行显式转换:
int rounded = (int) Math.round(5.6); // 6
3. 按指定位数进行小数舍入
很多时候需要舍入到非整数,比如保留两位小数(用于金额、百分比等)。
在 Java 中没有类似 Math.roundTo2Digits() 这样的“魔法”方法,但我们可以手动实现。
方法 1:乘除法的小技巧
- 先将数字乘以 100(如果需要两位)。
- 用 Math.round 将其舍入到整数。
- 再除以 100 还原。
示例:
double value = 3.14159;
double rounded = Math.round(value * 100.0) / 100.0;
System.out.println(rounded); // 3.14
原理:
- 3.14159 * 100 = 314.159
- Math.round(314.159) = 314
- 314 / 100.0 = 3.14
4. 数字格式化:类 DecimalFormat
有时不仅要对数字进行舍入,还需要以“漂亮”的样式输出——例如固定的小数位数、前导零、千位分隔符等。为此,Java 提供了位于包 java.text 中的 DecimalFormat 类。
DecimalFormat 的用法
- 使用所需的模式创建对象。
- 调用其方法 format(...)。
import java.text.DecimalFormat;
double value = 3.14159;
DecimalFormat df = new DecimalFormat("0.00");
System.out.println(df.format(value)); // 3.14
模式说明
- "0.00" — 始终保留两位小数(即使是整数)。
- "0.###" — 最多三位小数,多余的零不显示。
- "#,##0.00" — 添加千位分隔符(例如,"1,234.56")。
不同模式的示例:
| 模式 | 数字 | 结果 |
|---|---|---|
|
2 | 2.00 |
|
2.5 | 2.50 |
|
2.567 | 2.57 |
|
2.567 | 2.567 |
|
2.5 | 2.5 |
|
12345.678 | 12,345.68 |
带千位分隔符的示例
DecimalFormat df = new DecimalFormat("#,##0.00");
System.out.println(df.format(1234567.89)); // 1,234,567.89
示例:去除多余的零
DecimalFormat df = new DecimalFormat("0.##");
System.out.println(df.format(3.1)); // 3.1
System.out.println(df.format(3.141)); // 3.14
System.out.println(df.format(3.145)); // 3.15
5. 使用 String.format 进行格式化
在一些简单场景下,使用 String.format 很方便——这是一种内置的字符串格式化方式,类似于其他语言中的 printf。
double value = 3.14159;
System.out.println(String.format("%.2f", value)); // 3.14
这里 "%.2f" — 表示“以两位小数的形式输出数字”。
方法对比:
- DecimalFormat — 功能更强,支持模式和本地化,适合千位分隔符等。
- String.format — 更简单,适用于只需控制小数位数的情况。
6. 舍入的细节: Math.floor, Math.ceil, Math.rint
- Math.floor(x) — 向下取整(取不大于 x 的最大整数),对负数同样适用。
- Math.ceil(x) — 向上取整(取不小于 x 的最小整数)。
- Math.rint(x) — 四舍五入到最接近的整数,但返回 double。
示例:
System.out.println(Math.floor(2.7)); // 2.0
System.out.println(Math.ceil(2.1)); // 3.0
System.out.println(Math.rint(2.5)); // 2.0(没错,这不是笔误!)
System.out.println(Math.rint(3.5)); // 4.0
注意: Math.rint 有时会采用“舍入到最近的偶数”——这种规则(即 banker's rounding)有助于在大量计算中减少误差累积。
7. 实用细节
可视化:舍入对照表
| 数字 | Math.round | Math.floor | Math.ceil | Math.rint |
|---|---|---|---|---|
| 2.3 | 2 | 2.0 | 3.0 | 2.0 |
| 2.5 | 3 | 2.0 | 3.0 | 2.0 |
| 3.5 | 4 | 3.0 | 4.0 | 4.0 |
| -2.3 | -2 | -3.0 | -2.0 | -2.0 |
| -2.5 | -2 | -3.0 | -2.0 | -2.0 |
舍入 ≠ 格式化!
务必要理解 舍入 与 格式化 的区别。
- 舍入 —— 改变数值本身(例如,2.718 → 2.72)。
- 格式化 —— 只改变显示效果(例如,2.718 → "2.72" 出现在屏幕上,但内存中的值仍是 2.718)。
double x = 2.718;
System.out.println(String.format("%.2f", x)); // 2.72
System.out.println(x); // 2.718
如果你希望在后续计算中使用舍入后的数值——请进行数学上的舍入!如果只是为了展示——请进行格式化。
8. 舍入与格式化中的常见错误
错误 1:只为展示而舍入,后续仍使用“长尾”数值。
这是很常见的情况——屏幕上显示了漂亮的格式化结果,但在计算中仍然使用原始的“长”数字。对于金额、分数和汇总等场景,应先进行舍入(例如 Math.round 或乘除法技巧),再进行存储/传递给用户。
错误 2:以为 Math.round(x) 会返回保留两位小数的 double。
Math.round() 总是舍入到最接近的整数:传入 float 返回 int,传入 double —— long。若需两位小数,请使用乘除法或格式化。
错误 3:使用 DecimalFormat 参与计算。
DecimalFormat.format() 返回的是字符串。它不能直接用于算术运算:要么编译报错,要么在回解析时抛出 NumberFormatException。
错误 4:DecimalFormat 的模式不正确。
模式必须符合你的期望:"0.00" 总会显示两位("2.00"),而 "0.##" 会去掉多余的零("2"、"2.1"、"2.12")。
错误 5:按 100 相除时的精度丢失。
如果你用 int 除以 int,结果也会是 int!
int a = 5;
System.out.println(a / 2); // 2,而不是 2.5!
为了得到“小数”除法的结果,至少有一个操作数要是 double:
int a = 5;
System.out.println(a / 2.0); // 2.5
GO TO FULL VERSION