别在用BigDecimal给自己挖坑了!

前言

工作中,我们都会用到BigDecimal来进行金额计算,但是他有许多坑,可能针对新手不注意的话,就给自己多加几个bug了。一起来看看吧。

创建

new BigDecimal()还是BigDecimal#valueOf()

创建对象的时候应该使用BigDecimal.valueOf(0.01);
new BigDecimal()会有精度问题,所以建议使用字符串去创建对象而不是浮点类型,BigDecimal.valueOf()底层使用的就是用字符串去创建对象。确保精度不会丢失。

等值比较

BigDecimal中equals方法的实现会比较两个数字的精度,而compareTo方法则只会比较数值的大小。

public static void main(String[] args) {
    BigDecimal bigDecimal1 = new BigDecimal("1.0");
    BigDecimal bigDecimal2 = new BigDecimal("1.00");
    System.out.println(bigDecimal2.equals(bigDecimal1));
    System.out.println(bigDecimal2.compareTo(bigDecimal1));
}

运行结果:

BigDecimal并不代表无限精度

建议做除法等操作的时候,都写上保留位数和取值方式。

public static void main(String[] args) {
    BigDecimal bigDecimal1 = new BigDecimal("1.0");
    BigDecimal bigDecimal2 = new BigDecimal("3.0");
    bigDecimal1.divide(bigDecimal2);
}

运行结果:

BigDecimal转String要小心

public static void main(String[] args) {
    BigDecimal bigDecimal = BigDecimal.valueOf(12345678902132123113213.12345678912345678);
    //必要时,使用科学计数法
    System.out.println(bigDecimal.toString());
    //不使用科学计数法
    System.out.println(bigDecimal.toPlainString());
    //工程计算中经常使用的记录数字的方法,类似科学计数法,但要求是10的幂必须是3的倍数
    System.out.println(bigDecimal.toEngineeringString());
}

  • String toString(); // 有必要时使用科学计数法
  • String toPlainString(); // 不使用科学计数法
  • String toEngineeringString(); // 工程计算中经常使用的记录数字的方法,与科学计数法类似,但要求10的幂必须是3的倍数

执行顺序不能调换(乘法交换律失效)

乘法满足交换律是一个常识,但是在计算机的世界里,会出现不满足乘法交换律的情况

BigDecimal a = BigDecimal.valueOf(1.0);
BigDecimal b = BigDecimal.valueOf(3.0);
BigDecimal c = BigDecimal.valueOf(3.0);
System.out.println(a.divide(b, 2, RoundingMode.HALF_UP).multiply(c)); // 0.990
System.out.println(a.multiply(c).divide(b, 2, RoundingMode.HALF_UP)); // 1.00

别小看这这0.01的差别,在汇金领域,会产生非常大的金额差异。

最后有个关于金额计算的Money类

maven坐标

<dependency>
    <groupId>org.javamoney</groupId>
    <artifactId>moneta</artifactId>
    <version>1.1</version>
</dependency>

新建Money类

CurrencyUnit cny = Monetary.getCurrency("CNY");
Money money = Money.of(1.0, cny); 
// 或者 Money money = Money.of(1.0, "CNY");
//System.out.println(money);

金额运算

CurrencyUnit cny = Monetary.getCurrency("CNY");
Money oneYuan = Money.of(1.0, cny);
Money threeYuan = oneYuan.add(Money.of(2.0, "CNY")); //CNY 3
Money tenYuan = oneYuan.multiply(10); // CNY 10
Money fiveFen = oneYuan.divide(2); //CNY 0.5

比较相等

Money fiveFen = Money.of(0.5, "CNY"); //CNY 0.5
Money anotherFiveFen = Money.of(0.50, "CNY"); // CNY 0.50
System.out.println(fiveFen.equals(anotherFiveFen)); // true

可以看到,这个类对金额做了显性的抽象,增加了金额的单位,也避免了直接使用BigDecimal的一些坑。

总结

使用BigDecimal过程中,记住这些坑,使用正确的方法,让你少走弯路,少加几天班。

### BigDecimal 常见问题及解决方法 #### 处理浮点数运算精度丢失 为了处理浮点数运算中的精度丢失问题,推荐使用 `BigDecimal` 来替代基本数据类型的浮点数表示。创建 `BigDecimal` 对象时应始终采用字符串形式传入数值,以避免二进制转换带来的误差。 ```java // 正确做法:使用字符串初始化 BigDecimal a = new BigDecimal("1.0"); BigDecimal b = new BigDecimal("0.9"); // 错误做法:直接用 double 初始化可能导致精度损失 BigDecimal wrongA = new BigDecimal(1.0); ``` #### 除法运算异常处理 当执行除法操作时,如果不指定舍入模式和小数位数,则可能会遇到无限循环小数的情况,进而抛出 `ArithmeticException` 异常。因此,在调用 `divide()` 方法前需预先设定好这些参数[^3]。 ```java public static void safeDivideExample() { BigDecimal dividend = new BigDecimal("1"); BigDecimal divisor = new BigDecimal("3"); // 设置保留两位有效数字并四舍五入 BigDecimal quotient = dividend.divide(divisor, 2, RoundingMode.HALF_DOWN); } ``` #### 舍入模式的选择 不同的业务场景可能需要不同类型的舍入方式。Java 中提供了多种舍入策略供开发者选择: - **RoundingMode.UP**: 远离零方向舍入。 - **RoundingMode.DOWN**: 靠近零方向舍入。 - **RoundingMode.CEILING**: 向正无穷大方向舍入。 - **RoundingMode.FLOOR**: 向负无穷大方向舍入。 - **RoundingMode.HALF_UP / HALF_DOWN / HALF_EVEN**: 根据中间值的不同采取相应的向上或向下取整逻辑。 对于金融计算而言,默认建议选用 `HALF_UP` 或者 `HALF_EVEN`(银行家算法),因为这两种方案能够较好地平衡统计偏差的影响[^5]。 #### 工具类封装 考虑到实际开发过程中频繁涉及的大数运算需求,可以考虑编写一套基于 `BigDecimal` 的辅助函数库,简化日常编码工作量的同时提高代码可读性和维护性。下面是一个简单的加减乘除实现例子: ```java import java.math.BigDecimal; import java.math.RoundingMode; public class BigDecimalUtil { private static final int SCALE = 2; // 默认保留两位小数 /** * 加法运算 */ public static BigDecimal add(BigDecimal v1, BigDecimal v2){ return v1.add(v2).setScale(SCALE,RoundingMode.HALF_UP); } /** * 减法运算 */ public static BigDecimal sub(BigDecimal v1, BigDecimal v2){ return v1.subtract(v2).setScale(SCALE,RoundingMode.HALF_UP); } /** * 乘法运算 */ public static BigDecimal mul(BigDecimal v1, BigDecimal v2){ return v1.multiply(v2).setScale(SCALE,RoundingMode.HALF_UP); } /** * 除法运算 */ public static BigDecimal div(BigDecimal v1, BigDecimal v2,int scale){ if (scale < 0) throw new IllegalArgumentException("The scale must be greater than or equal to zero."); return v1.divide(v2,scale,RoundingMode.HALF_UP); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值