一、前言
在 Java 中,基本数据类型 `float` 和 `double` 采用二进制浮点数存储,无法精确表示所有十进制小数(例如 `0.1`),进行算术运算时容易出现精度丢失问题(如 `0.1 + 0.2 != 0.3`)。而 `BigDecimal` 类位于 `java.math` 包中,专门用于处理**高精度的十进制数值运算**,能够完美解决浮点数精度丢失问题,广泛应用于金融、电商、财务等对数值精度要求极高的场景。
二、核心特点
- **精度无损**:基于十进制整数和缩放因子(小数点位置)实现存储,能够精确表示任意十进制小数,不存在二进制浮点数的精度误差。 2. **支持超大数值**:突破了 `float`、`double` 以及基本整数类型的数值范围限制,可存储和运算超大位数的十进制数值。 3. **丰富的运算方法**:提供了加减乘除、取余、幂运算、四舍五入等全套算术运算和数值处理方法,满足复杂的数值计算需求。 4. **不可变性**:与 `String` 类类似,`BigDecimal` 对象一旦创建,其内部存储的数值就无法修改,所有运算操作都会返回一个新的 `BigDecimal` 对象。
三、创建 BigDecimal 对象(推荐方式 & 避坑)
创建 `BigDecimal` 有多种方式,核心是**避免使用 `double` 类型参数的构造器**,防止精度丢失从源头发生。 ### 1. 推荐创建方式 - **方式1:使用 `String` 类型参数的构造器**(最常用、最安全,无精度丢失) ```java BigDecimal bd1 = new BigDecimal("0.1"); BigDecimal bd2 = new BigDecimal("100.000"); BigDecimal bd3 = new BigDecimal("-99.99"); ``` - **方式2:使用静态方法 `valueOf(double val)`**(底层会将 `double` 转换为精确的 `String`,规避直接使用 `double` 构造器的坑) ```java BigDecimal bd4 = BigDecimal.valueOf(0.1); BigDecimal bd5 = BigDecimal.valueOf(123.456); ``` - **方式3:使用静态常量获取常用数值**(JDK 提供的预设常量,性能更高) ```java BigDecimal zero = BigDecimal.ZERO; // 0 BigDecimal one = BigDecimal.ONE; // 1 BigDecimal ten = BigDecimal.TEN; // 10 ``` ### 2. 不推荐创建方式 - 避免使用 `new BigDecimal(double val)` 构造器:由于 `double` 本身存在精度误差,传入后会保留该误差,导致 `BigDecimal` 也无法精确表示。 ```java // 反面示例:结果并非 0.1,而是一个近似值 BigDecimal bdError = new BigDecimal(0.1); System.out.println(bdError); // 输出:0.1000000000000000055511151231257827021181583404541015625 ```
四、核心算术运算方法
`BigDecimal` 不支持使用 `+`、`-`、`*`、`/` 等运算符进行运算,必须通过自身提供的实例方法完成,所有方法均返回新的 `BigDecimal` 对象,原对象保持不变。 ### 1. 加法:`add(BigDecimal augend)` 用于计算两个 `BigDecimal` 的和,参数为被加数。 ```java BigDecimal a = new BigDecimal("10.5"); BigDecimal b = new BigDecimal("20.3"); BigDecimal sum = a.add(b); // 结果:30.8 ``` ### 2. 减法:`subtract(BigDecimal subtrahend)` 用于计算两个 `BigDecimal` 的差,参数为减数。 ```java BigDecimal a = new BigDecimal("50.0"); BigDecimal b = new BigDecimal("18.7"); BigDecimal diff = a.subtract(b); // 结果:31.3 ``` ### 3. 乘法:`multiply(BigDecimal multiplicand)` 用于计算两个 `BigDecimal` 的积,参数为乘数。 ```java BigDecimal a = new BigDecimal("3.14"); BigDecimal b = new BigDecimal("2.5"); BigDecimal product = a.multiply(b); // 结果:7.85 ``` ### 4. 除法:`divide(BigDecimal divisor, int scale, RoundingMode roundingMode)` 除法是最复杂的运算,容易出现除不尽的情况,因此推荐使用带精度和舍入模式的重载方法,避免抛出 `ArithmeticException`(算术异常)。 - 参数说明: 1. `divisor`:除数; 2. `scale`:保留的小数位数; 3. `RoundingMode`:舍入模式(枚举类型,指定小数的取舍规则)。 - 常用舍入模式: - `RoundingMode.HALF_UP`:四舍五入(日常开发最常用,如 0.125 保留 2 位小数为 0.13); - `RoundingMode.DOWN`:直接舍弃多余小数(如 0.129 保留 2 位小数为 0.12); - `RoundingMode.UP`:向上进 1(如 0.121 保留 2 位小数为 0.13); - `RoundingMode.HALF_EVEN`:银行家舍入法(四舍六入五取偶,金融场景常用)。 - 示例: ```java BigDecimal a = new BigDecimal("10"); BigDecimal b = new BigDecimal("3"); // 10 ÷ 3,保留 2 位小数,四舍五入 BigDecimal quotient = a.divide(b, 2, RoundingMode.HALF_UP); // 结果:3.33 ``` ### 5. 其他常用运算 - 取余:`remainder(BigDecimal divisor)`,返回除法运算的余数; - 幂运算:`pow(int n)`,返回当前数值的 n 次幂; - 绝对值:`abs()`,返回当前数值的绝对值; - 取反:`negate()`,返回当前数值的相反数。
五、数值格式化与舍入处理
- 单独设置舍入(针对已有 `BigDecimal`) 使用 `setScale(int scale, RoundingMode roundingMode)` 方法,用于设置数值的小数位数和舍入模式,返回新的 `BigDecimal` 对象。 ```java BigDecimal bd = new BigDecimal("123.456789"); // 保留 2 位小数,四舍五入 BigDecimal formatted1 = bd.setScale(2, RoundingMode.HALF_UP); // 结果:123.46 // 保留 3 位小数,直接舍弃多余部分 BigDecimal formatted2 = bd.setScale(3, RoundingMode.DOWN); // 结果:123.456 ``` ### 2. 复杂格式化(搭配 `DecimalFormat`) 若需要自定义数值格式(如添加千分位、货币符号、百分比格式),可搭配 `java.text.DecimalFormat` 使用。 ```java import java.text.DecimalFormat; BigDecimal amount = new BigDecimal("123456.789"); // 定义格式:千分位分隔,保留 2 位小数 DecimalFormat df = new DecimalFormat("#,###.00"); String formatStr = df.format(amount); // 结果:123,456.79 ```
六、比较大小
`BigDecimal` 不能使用 `==` 进行相等判断(`==` 比较的是对象地址,而非数值),也不推荐使用 `compareTo` 以外的方法,核心使用 `compareTo(BigDecimal val)` 方法。 ### 1. `compareTo` 方法返回值说明 - 返回 `0`:表示当前 `BigDecimal` 与参数 `val` 数值相等; - 返回 `1`:表示当前 `BigDecimal` 大于参数 `val`; - 返回 `-1`:表示当前 `BigDecimal` 小于参数 `val`。 ### 2. 示例 ```java BigDecimal bd1 = new BigDecimal("100.00"); BigDecimal bd2 = new BigDecimal("100"); BigDecimal bd3 = new BigDecimal("99.99"); // 数值相等(忽略小数点后多余的 0) int cmp1 = bd1.compareTo(bd2); // 返回 0 // bd1 大于 bd3 int cmp2 = bd1.compareTo(bd3); // 返回 1 // bd3 小于 bd2 int cmp3 = bd3.compareTo(bd2); // 返回 -1 ``` ### 3. 辅助判断:`equals()` 方法 `equals()` 方法不仅比较数值,还会比较小数位数(缩放因子),仅当数值和小数位数都相同时才返回 `true`,使用时需注意。 ```java BigDecimal bd1 = new BigDecimal("100.00"); BigDecimal bd2 = new BigDecimal("100"); System.out.println(bd1.equals(bd2)); // 输出:false(小数位数不同) System.out.println(bd1.compareTo(bd2) == 0); // 输出:true(仅比较数值) ```
七、类型转换
- 转换为基本数据类型 - `intValue()`:转换为 `int`(超出范围会丢失精度); - `longValue()`:转换为 `long`; - `doubleValue()`:转换为 `double`(可能出现精度丢失,谨慎使用); - `BigInteger toBigInteger()`:转换为 `BigInteger`(舍弃小数部分,获取整数部分)。 ### 2. 转换为 `String` 类型 - `toString()`:返回常规的字符串表示形式; - `toPlainString()`:返回无科学计数法的字符串(推荐,避免超大/超小数值显示为科学计数法)。 ```java BigDecimal bd = new BigDecimal("1234567890.123456789"); String str1 = bd.toString(); // 正常输出:1234567890.123456789 BigDecimal bd2 = new BigDecimal("0.000000123"); String str2 = bd2.toPlainString(); // 输出:0.000000123(避免科学计数法) ```
八、使用注意事项
- 优先使用 `String` 构造器或 `valueOf(double)` 方法创建对象,避免 `double` 构造器的精度丢失。 2. `BigDecimal` 是不可变对象,所有运算操作都会生成新对象,避免重复创建无用对象,提升性能。 3. 除法运算必须指定舍入模式和小数位数,否则除不尽时会抛出 `ArithmeticException`。 4. 比较数值是否相等时,优先使用 `compareTo()` 方法,而非 `equals()` 方法(避免小数位数干扰)。 5. 在金融场景中,推荐使用 `RoundingMode.HALF_EVEN`(银行家舍入法),减少长期结算的误差。 6. 若需频繁进行大量 `BigDecimal` 运算,注意对象的复用,避免内存溢出和性能损耗。
九、总结
- `BigDecimal` 是 Java 中处理高精度十进制数值的核心类,解决了 `float`/`double` 的精度丢失问题,适用于精度敏感场景。 2. 核心要点是正确创建对象(规避 `double` 构造器)、熟练使用算术运算方法、合理设置舍入模式。 3. 比较大小用 `compareTo()`,格式化用 `setScale()` 或 `DecimalFormat`,类型转换需注意精度风险。 4. 遵循使用规范,能够有效保证数值运算的准确性和安全性,是金融、财务等开发场景的必备工具类。