一、引进
做算法题的时候,最气人的不是不会写,而是会写却算错。明明逻辑满分,一输出"0.30000000000000004",瞬间被判死刑。别怀疑,就是 double 在背后捅刀------它眼里 0.1+0.2 永远不等于 0.3。
想甩掉这坑,只能把 double 踢下线,让 BigDecimal 上岗。它不讲二进制那一套,就按我们十进制算盘打,指哪打哪,小数点后面想留几位就留几位,永远对得上账。只要内存够,它能跟你耗到你先倒下,而且精度一分不差。
面对涉及金融计算、超大整数、或者需要绝对精确小数运算的题目时,
使用 BigDecimal
二、优点
1.算得准
double 眼里 0.1 其实是"0.100000000000000005...",攒多了就翻车。BigDecimal 把数字当"字符串"存,小学竖式怎么算它就怎么算,说 0.1 就 0.1,一分不差。
2. 数字再大也装得下
long 最大才 9 后面 18 个数字,题目动不动就要算 100 位的阶乘?BigDecimal 直接把数字当"无限长"的数组,只要内存不炸,它就能一直写下去。
3. 想怎么四舍五入你说了算
题目要"保留两位小数""银行家舍入""直接砍尾"?一行 setScale+RoundingMode,分分钟切好,double 却只能干瞪眼。
4. 绝不溢出
int、long 加着加着变负数?放心,BigDecimal 只要内存有多大,我就能算多大。
三、算法题中最常用的BigDecimal方法
构造方法:
BigDecimal(String val) : 最推荐、最安全的方式! 使用字符串构造可以精确表示你想要的数值
(如 new BigDecimal("0.1"))。避免使用BigDecimal(double val),因为传入的double本身可能已有精度损失(如 new BigDecimal(0.1) 实际上构造的是
0.1000000000000000055511151231257827021181583404541015625)。
BigDecimal.valueOf(double val): 这是将double转换为BigDecimal的相对安全方式。它内部调用Double.toString(double),利用了字符串转换的精确性。适用于已知double值能精确表示或精度要求高的场景(比直接使用double构造器好得多)。
算术运算:
add(BigDecimal augend): 加法
subtract(BigDecimal subtrahend): 减法
multiply(BigDecimal multiplicand): 乘法
divide(BigDecimal divisor): 除法(注意: 如果结果无法精确表示(如1 / 3),会抛出ArithmeticException ,必须指定舍入模式!) divide(BigDecimal divisor, int scale, RoundingMode roundingMode): 最常用的除法形式!
指定结果的小数位数(scale)和舍入模式(roundingMode)。
divide(BigDecimal divisor, RoundingMode roundingMode): 除法,使用指定的舍入模式,结果精度由计算决定(通常位数较多)
abs(): 绝对值pow(int n): 幂运算(n次方)
比较:
compareTo(BigDecimal val): 比较大小的标准方法
返回 -1(小于)、0(等于)、1(大于)。必须使用此方法代替equals()!
重要陷阱: equals()方法不仅比较数值是否相等 ,还比较精度(scale)是否相同 。
new BigDecimal("1.0").equals(new BigDecimal("1.00")) 会返回 false!
而compareTo() 只关心数值本身,1.0和1.00比较结果为0(相等)。
精度控制与舍入:
setScale(int newScale, RoundingMode roundingMode) : 核心精度控制方法! 设置小数点后的位数 (newScale),并使用指定的RoundingMode 进行必要的舍入操作。
如果当前scale大于newScale,需要舍入; 如果小于newScale,则在末尾补零。
scale(): 获取当前小数点后的位数。
输出:
toString():
返回此BigDecimal的规范字符串表示形式。如果需要特定格式(如固定小数位、科学计数法),应使用NumberFormat或DecimalFormat。
toPlainString():返回不带指数字段的字符串表示。对于非常大或非常小的数,toString()可能输出科学计数法(如1e+10),而toPlainString()会输出"10000000000"。
四、RoundingMode:定义如何"舍"与"入"
RoundingMode是一个枚举类(java.math.RoundingMode),定义了8种舍入策略。它与BigDecimal的divide和setScale方法紧密结合,是精确控制结果的关键。算法竞赛中最常用的几种模式:
-
RoundingMode.HALF_UP ("四舍五入"):
- 规则: 舍弃部分 >= 0.5 时进位;< 0.5 时舍弃。
- 例子: 5.5 -> 6, 2.5 -> 3, 1.4 -> 1, 1.6 -> 2
- 谐音:"哈夫上"
画面:哈(HALF)佛老教授走上(UP)讲台------只上一半台阶就回头,"过半就上去"。 - 竞赛应用: 题目明确要求"四舍五入"时使用。这是最符合直觉的舍入方式。
-
RoundingMode.DOWN ("向零舍入"):
- 规则: 无论舍弃部分大小,总是向零的方向舍入(即绝对值变小)。
- 例子: 5.9 -> 5, -5.9 -> -5, 1.1 -> 1, -1.1 -> -1
- 谐音:"剁"
画面:一把刀剁下去,向零剁,数字瞬间被砍到 0 那边。 - 竞赛应用: 需要截断小数部分(取整)而不进行任何舍入时使用。类似于强制类型转换(int)的效果。
-
RoundingMode.CEILING ("向正无穷舍入"):
- 规则: 结果总是向正无穷方向舍入(向上取整)。
- 例子: 5.1 -> 6, -5.1 -> -5, 1.0 -> 1
- 竞赛应用: 需要计算"至少需要多少个"的场景(如物品装箱、资源分配)。
-
RoundingMode.FLOOR ("向负无穷舍入"):
- 规则: 结果总是向负无穷方向舍入(向下取整)。
- 例子: 5.9 -> 5, -5.9 -> -6, 1.0 -> 1
- 竞赛应用: 需要计算"最多能容纳多少个"的场景。
-
RoundingMode.HALF_EVEN ("银行家舍入法"):
- 规则: 舍弃部分 > 0.5 时进位;< 0.5 时舍弃;等于 0.5 时,看前一位数字:如果是奇数则进位,如果是偶数则舍弃。
- 例子: 5.5 -> 6 (5是奇数,进位), 2.5 -> 2 (2是偶数,舍弃), 1.6 -> 2, 1.4 -> 1
- 竞赛应用: 在统计学或需要减少累计误差的场景中更优。题目没有特别说明"四舍五入"时,有时会默认使用此规则(需仔细审题!)
五、在算法题中使用BigDecimal的典型流程
-
构造 :
优先使用String 构造或BigDecimal.valueOf()
-
运算 :
使用add, subtract, multiply 进行基本运算。进行divide 时,一定要指定精度(scale)和舍入模式(RoundingMode)!
-
精度控制 :
在最终输出结果前,使用setScale(int desiredScale, RoundingMode roundingMode)将 结果调整到题目要求的精度。
-
比较 :
使用compareTo()比较两个BigDecimal的大小。
-
输出 :
使用toString()或toPlainString()输出,或者根据题目要求使用格式化输出。
例题
计算圆的面积,保留两位小数,四舍五入
java
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Scanner;
public class CircleArea {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
BigDecimal radius = new BigDecimal(sc.nextDouble());
BigDecimal PI = new BigDecimal("3.14159");
//计算面积
BigDecimal area = PI.multiply(radius).multiply(radius);
//设置精度,保留两位小数,四舍五入
area = area.setScale(2,RoundingMode.HALF_UP);
System.out.println(area);
//输入:5.0
//输出:78.54
}
}