BigDecimal讲解

前言

在 Java 开发中,涉及到金额计算、财务系统、电商报价等对数字精度要求极高的场景时,doublefloat 往往会成为制造 Bug 的罪魁祸首。

此时,就会使用这个类 —— BigDecimal

为什么不能用 double 算钱?

代码如下:

1
2
3
4
5
System.out.println(0.1 + 0.2); 
// 输出结果: 0.30000000000000004

System.out.println(1.0 - 0.9);
// 输出结果: 0.09999999999999998

原因解析: 计算机底层使用二进制表示浮点数,而许多十进制小数(如 0.1)在二进制中是无限循环小数。double 会在某一位截断,从而导致精度丢失。在商业计算中,差一分钱也是极其严重的生产事故。

为了解决这个问题,Java 提供了 java.math.BigDecimal,专用于高精度计算。


一、 创建BigDecimal对象

创建 BigDecimal 对象时,最容易踩进“精度丢失”的二次陷阱。

1.1 错误用法

1
2
3
BigDecimal bad = new BigDecimal(0.1); 
System.out.println(bad);
// 输出: 0.1000000000000000055511151231257827021181583404541015625

原因:传入的 0.1 已经是 double 类型,在传参前就已经丢失精度了。

1.2 正确推荐用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 推荐 1:使用字符串构造(最安全、最常用)
BigDecimal good1 = new BigDecimal("0.1");
System.out.println(good1); // 输出0.1

// 推荐 2:使用 valueOf 方法(内部会将 double 转为 String)
BigDecimal good2 = BigDecimal.valueOf(0.1);
System.out.println(good1); // 输出0.1

// 常量推荐:使用内置常量
BigDecimal zero = BigDecimal.ZERO;
BigDecimal one = BigDecimal.ONE;
BigDecimal ten = BigDecimal.TEN;
System.out.println("输出BigDecimal常量:zero:" + zero + ",one:" + one + ", ten:" + ten);
// 输出BigDecimal常量:zero:0,one:1, ten:10

二、 核心运算:加减乘除

BigDecimal 是一个对象,不可变(Immutable),所以不能使用 + - * / 操作符,必须调用对应的方法,并且每次运算都会返回一个新的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BigDecimal a = new BigDecimal("10.0");
BigDecimal b = new BigDecimal("3.0");

// 加法 (Add)
BigDecimal sum = a.add(b); // 13.0

// 减法 (Subtract)
BigDecimal diff = a.subtract(b); // 7.0

// 乘法 (Multiply)
BigDecimal mul = a.multiply(b); // 30.00

// 除法 (Divide) - ⚠️ 高危操作
// 如果除不尽(如 10/3),不指定舍入模式会直接抛出 ArithmeticException 异常!
BigDecimal div = a.divide(b, 2, RoundingMode.HALF_UP); // 3.33 (保留2位小数,四舍五入)

三、 舍入模式 (RoundingMode)

在除法或格式化输出时,我们通常需要保留小数位数。RoundingMode 枚举类提供了多种规则,最常用的有:

  • RoundingMode.HALF_UP四舍五入(最常用,如 2.35 保留一位 -> 2.4)。
  • RoundingMode.HALF_DOWN:五舍六入(遇到 5 往下舍弃)。
  • RoundingMode.UP:向上取整(只要有小数就进位)。
  • RoundingMode.DOWN:向下取整(直接截断尾数,不进位)。

四、 比较大小:慎用 equals

在对比两个 BigDecimal 的大小时,绝对不要轻易使用 equals(),而应该使用 compareTo()

Java

1
2
3
4
5
6
7
8
9
BigDecimal x = new BigDecimal("1.0");
BigDecimal y = new BigDecimal("1.00");

// ❌ equals 会比较“值”和“精度(scale)”
System.out.println(x.equals(y)); // false,因为精度一个是1,一个是2

// ✅ compareTo 只比较“值”的大小
// 返回 0 表示相等;-1 表示小于;1 表示大于
System.out.println(x.compareTo(y) == 0); // true

总结

  1. 在 Java 中,金额、财务等高精度计算严禁使用 double 或 float,因其存在二进制浮点精度丢失。

  2. 必须使用 BigDecimal,创建时推荐字符串构造(new BigDecimal(“0.1”))或 valueOf,避免传入 double。

  3. 运算只能调用 add()、subtract()、multiply()、divide() 方法,除法必须指定小数位数和 RoundingMode(常用 HALF_UP)。

  4. 比较大小只能用 compareTo(),不要用 equals(),因为 equals 会同时比较精度 scale。