W
e
l
c
o
m
e
: )

Java的BigDecimal类

一、精度丢失问题

先来看下面的这段浮点数运算的代码:

//0.1 + 0.2 不等于 0.3
System.out.println(0.1 + 0.2); // 输出:0.30000000000000004

我们发现输出结果并不是我们所预想的 0.3,这是因为计算机是采用二进制进行运算的,但是计算机可以表示的二进制的宽度是有限的,许多十进制小数转为二进制之后会无限循环,没办法完整表示,只能舍入为有限位数,所以会带来精度丢失的问题。

二、BigDecimal的使用

BigDecimal 是 Java 中用于高精度十进制运算的类,位于 java.math 包。它是金融、电商、计费系统中绝对不可或缺的核心工具。下面我们来看看具体如何使用:

一、创建实例

需要注意的是,不要使用 new BigDecimal(double),因为 double 本身已经失真了,传进去只会得到一个“失真后的垃圾值”。

BigDecimal d1 = new BigDecimal(0.1);//0.1000000000000000055511151231257827021181583404541015625

推荐使用 String 构造器或 BigDecimal.valueOf()

BigDecimal d2 = new BigDecimal("0.1");
BigDecimal d3 = BigDecimal.valueOf(0.1);

二、核心运算

public class Main {
    public static void main(String[] args) {
        BigDecimal a = new BigDecimal("10.00");
        BigDecimal b = new BigDecimal("3.00");

        BigDecimal sum = a.add(b);          // 加法:13.00
        BigDecimal diff = a.subtract(b);    // 减法:7.00
        BigDecimal mul = a.multiply(b);     // 乘法:30.0000 (精度会变多)
        BigDecimal div = a.divide(b, 2, RoundingMode.HALF_UP); // 除法:3.33 (必须指定精度和舍入)
        BigDecimal rem = a.remainder(b);    // 取余:1.00
    }
}

这里有两个问题需要注意:首先是乘法运算,我们发现最终的运算结果精度会变多,这是因为 BigDecimal 的乘法规则是“精度(小数位数)相加”,而不是像加减法那样“精度对齐”。我们先来看一下 BigDecimal的底层构成:

  • int scale : 小数位数 (即小数点后有几位)。
  • BigInteger intVal : 去除小数点后的整数值 (有效数字)。

它的实际值计算公式是:intVal × 10^(-scale)

举例:

  • 10.00 存储为:intVal = 1000scale = 2(即 1000 × 10⁻²)
  • 3.00 存储为:intVal = 300scale = 2(即 300 × 10⁻²)

做乘法时,Java 遵循基本的数学算术规则: 两个小数相乘,结果的小数位数等于两个乘数小数位数之和

即:结果scale = 乘数1.scale + 乘数2.scale

拿上面的例子代入计算:

  • 10.00(scale=2) × 3.00(scale=2)
  • 底层计算:1000 × 300 = 300000
  • 结果scale:2 + 2 = 4
  • 最终结果:300000 × 10⁻⁴ = 30.0000 (精度从2位变成了4位)

如果希望去除多余的末尾 0,统一保留 2 位小数,我们可以在运算结束后,调用 setScale() 进行归一化处理。

BigDecimal a = new BigDecimal("10.00");
BigDecimal b = new BigDecimal("3.00");

BigDecimal result = a.multiply(b); // 结果是 30.0000

// 归一化:保留2位小数,四舍五入
BigDecimal normalized = result.setScale(2, RoundingMode.HALF_UP); 
System.out.println(normalized); // 输出 30.00

如果你就是想彻底去掉末尾所有无意义的 0(比如 30.0000 -> 30),可以用 result.stripTrailingZeros(),它会返回精度最小的 BigDecimal

//注意:当数值非常大或非常小时,toString() 会返回科学计数法表示,我们可以使用toPlainString()转为纯字符串
System.out.println(result.stripTrailingZeros());//3E+1
System.out.println(result.stripTrailingZeros().toPlainString());//30

第二个就是除法运算,我们上面指定了精度和舍入,这是因为如果不指定的话,divide 在遇到无限循环小数(如 10/3)时会直接抛出 ArithmeticException

常用的舍入模式:

  • HALF_UP:四舍五入。
  • HALF_DOWN:五舍六入。
  • HALF_EVEN:四舍六入五成双(银行家舍入法,用于减少误差累积,美国会计准则常用)。
  • UP / DOWN:远离零 / 趋向零(相当于绝对值向上/向下)。

三、比较大小

在进行大小比较的时候,需要注意如下区别:

  • equals:比较值 + 精度(小数位数)1.01.00 会被判定为不相等.
  • compareTo:只比较数值大小1.01.00 数值相等,返回 0
BigDecimal x = new BigDecimal("1.0");
BigDecimal y = new BigDecimal("1.00");

System.out.println(x.equals(y));   // false(因为精度不同)
System.out.println(x.compareTo(y)); // 0(表示数值相等)

四、注意事项

  • BigDecimal 是不可变对象(Immutable),每一次运算都会返回一个新的 BigDecimal,原对象不变。
  • 使用 String 构造器 ,绝不用 double 构造器。
  • 做除法时,必须指定精度和舍入模式 。
  • 比较数值用 compareTo
  • 复用静态常量 :对于 0110100 等常用值,使用 BigDecimal.ZEROBigDecimal.ONEBigDecimal.TEN,避免重复 new 对象。
  • 累加优化 :如果需要对大量数据进行求和,不要用 BigDecimal 做循环 add(会产生大量临时对象)。建议先用 BigDecimal[] 收集,最后统一累加;或者转为 double 先做粗略计算后再转回(仅限非金融严格场景)。
posted @ 2026-08-03 15:28  寒月静无光  阅读(2)  评论(0)    收藏  举报