用了BigDecimal就不会资损?了解下BigDecimal这五个坑
往期热门文章:
1、一个依赖搞定 Spring Boot 反爬虫,防止接口盗刷!
BigDecimal
而导致故障的文章,但是除非在一些非常简单的场景,结算汇金类的业务也不会直接用BigDecimal
来计算金额,原因有两点:BigDecimal
里面还是有很多隐蔽的坑的BigDecimal
没有提供金额的单位
1. BigDecimal
中的五个容易踩的坑
1.1 new BigDecimal()
还是BigDecimal#valueOf()
?
BigDecimal bd1 = new BigDecimal(0.01);
BigDecimal bd2 = BigDecimal.valueOf(0.01);
System.out.println("bd1 = " + bd1);
System.out.println("bd2 = " + bd2);
bd1 = 0.01000000000000000020816681711721685132943093776702880859375
bd2 = 0.01
BigDecimal
的时候就已经丢精度了,而BigDecimal#valueOf
的实现却完全不同public static BigDecimal valueOf(double val) {
// Reminder: a zero double returns '0.0', so we cannot fastpath
// to use the constant ZERO. This might be important enough to
// justify a factory approach, a cache, or a few private
// constants, later.
return new BigDecimal(Double.toString(val));
}
BigDecimal
对象,因此避免了精度问题。所以大家要尽量要使用字符串而不是浮点数去构造BigDecimal
对象,如果实在不行,就使用BigDecimal#valueOf()
方法吧。1.2 等值比较
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
System.out.println(bd1.equals(bd2));
System.out.println(bd1.compareTo(bd2));
false
0
BigDecimal
中equals
方法的实现会比较两个数字的精度,而compareTo
方法则只会比较数值的大小。1.3 BigDecimal
并不代表无限精度
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("3.0");
a.divide(b) // results in the following exception.
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
JVM
我们不需要返回精确的结果就好了BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("3.0");
a.divide(b, 2, RoundingMode.HALF_UP)// 0.33
1.4 BigDecimal
转回String
要小心
BigDecimal d = BigDecimal.valueOf(12334535345456700.12345634534534578901);
String out = d.toString(); // Or perform any formatting that needs to be done
System.out.println(out); // 1.23345353454567E+16
BigDecimal
有三个方法可以转为相应的字符串类型,切记不要用错:String toString(); // 有必要时使用科学计数法
String toPlainString(); // 不使用科学计数法
String toEngineeringString(); // 工程计算中经常使用的记录数字的方法,与科学计数法类似,但要求10的幂必须是3的倍数
1.5 执行顺序不能调换(乘法交换律失效)
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
2. 最佳实践
BigDecimal
再封装一个Money
类,其实我们直接可以用一个半官方的Money
类:JSR 354 ,虽然没能在Java 9
中成为Java
标准,很有可能集成到后续的Java
版本中成为官方库。2.1 maven
坐标
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.1</version>
</dependency>
2.2 新建Money
类
CurrencyUnit cny = Monetary.getCurrency("CNY");
Money money = Money.of(1.0, cny);
// 或者 Money money = Money.of(1.0, "CNY");
//System.out.println(money);
2.3 金额运算
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
2.4 比较相等
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
的一些坑。往期热门文章:
1、线上MySQL的自增id用尽怎么办?被面试官干趴下了! 2、计算机专业会不会成为下一个土木? 3、xxl-job惊艳的设计,怎能叫人不爱 4、ArrayList#subList这四个坑,一不小心就中招 5、面试官:大量请求 Redis 不存在的数据,从而影响数据库,该如何解决? 6、MySQL 暴跌! 7、超越 Xshell!号称下一代 Terminal 终端神器,用完爱不释手! 8、IDEA 官宣全新默认 UI,太震撼了!! 9、让你直呼「卧槽」的 GitHub 项目! 10、Kafka又笨又重,为啥不选Redis?
评论