你知道的越多,不知道的就越多,业余的像一棵小草!
你来,我们一起精进!你不来,我和你的竞争对手一起精进!
编辑:业余草
zhuanlan.zhihu.com/p/94144867
推荐:https://www.xttblog.com/?p=5116
一律使用 BigDecimal,避免后患?
一、背景
总在项目中看到 Double 与 BigDecimal 被用错的情况,竟然有人告诉我:“一律使用 BigDecimal,避免后患”,我相信这位兄弟肯定是被精度问题搞蒙了,因此我想同步一下我的使用姿势,仅提供参考。
二、两种类型使用过程中都有可能出坑
- double 的计算时容易出现不精确的问题
double 的小数部分容易出现使用二进制无法准确表示。
如十进制的0.1,0.2,0.3,0.4 都不能准确表示成二进制。
具体原因相信大家都懂,我就不多说了。如果有不懂的可以私信我或者评论留言!
- dobule 的 = 比较要注意
dobule 小数超过 15 位的相等比较就不一定对了。都是 true。
- BigDecimal 的除法除不尽会出现异常:ArithmeticException
所以,使用 BigDecimal 进行除法运算一定要有精度!
- new BigDecimal(double) 结果也许不是你想要的
一般情况下都不使用 new BigDecimal(double) 应该使用 BigDecimal.valueOf(double)。
BigDecimal d1 = BigDecimal.valueOf(12.3);
//结果是12.3 你预期的
BigDecimal d2 = new BigDecimal(12.3);
//结果是12.300000000000000710542735760100185871124267578125
我想 12.300000000000000710542735760100185871124267578125 肯定不是你想要的结果,因此 new BigDecimal(double) 可能会产生不是你预期的结果,原理可以自行看一下底层源代码,还是比较容易搞懂的。
另:BigDecimal.valueOf(xxx) 是静态工厂类,永远优先于构造函数(摘自《Effecitve java》,此书也是非常推荐的一本经典书)
- BigDecimal 是不可变对象
如原来 d1=1.11 ,又加了一个数 2.11,这种操作要注意结果要指向新对象。
任何针对BigDecimal对象的修改都会产生一个新对象;
BigDecimal newValue = BigDecimal.valueOf(1.2222).add(BigDecimal.valueOf(2.33333));
BigDecimal newValue = BigDecimal.valueOf(1.2222).setScale(2);
总之每次修改都要重新指向新对象,才能保证计算结果是对的。
- BigDecimal 比较大小操作不方便,毕竟是对象操作
比较大小和相等都使用 compareTo,如果需要返回大数或小数可使用 max,min。且注意不能使用 equals。
三、效率比较
比如:1 累加到 1000000(以本人机器 MacBookPro 2018 i7 2.2G)double 比 BigDecimal 快大约 10 倍
double: 2ms
BigDecimal:16ms
四、优缺点总结
double 的优缺点:
double在计算过程中容易出现丢失精度问题
使用方便,有包装类,可自动拆装箱,计算效率高
BigDecimal 的优缺点:
精度准确,但做除法时要注意除不尽的异常
BigDecimal 是对象类型,也没有自动拆封箱机制,操作起来总是有些不顺手
五、使用场景推荐
涉及到精准计算如金额,一定要使用 BigDecimal 或转成 long 或 int 计算。
若不需要精准的,如一些统计值:(本身就没有精确值)。
用户平均价格,店铺评分,用户经纬度等本就没有精准值一说的推荐使用 double 或 float,写代码更方便,计算效率也高得多。
值得一提的说,如果 double 或 float 仅是用于传值,并不会有精度问题,但如果参与了计算就要小心了。要区分是不是需要精准值,如果需要精准值,需要转成 BigDecimal 计算以后再转成 double。
但依然约定在 DTO 定义金额时使用 BigDecimal 或整形值,是为了减少或避免 double 参与金额计算的机会,避免出 bug。
其他1:代码中看到碰到让我觉得有问题的地方
以下代码在不同的类中抓出来的觉得用得不太恰当的地方:
代码中真的不需要那么多地方使用 BigDecimal,相反用到 BigDecimal 的地方并不多,反而用 Double 的地方更多。以上代码我希望的方式是:
提醒:DTO 中尽量使用包装类,防止反系列化时 null 的造成的格式转换异常
分析
经纬度:一般业务代码中也不太会去计算,仅用于传给地图api等,经纬度一般用于计算距离,如果保留到 6 位小数时其实已经是 1 米级别的了,也满足绝大多数场景了,因此使用 Double 是确实是可行的;
店铺平均消费:本身就是一个归纳统计值,也一般用来比较大小做参考,因此也用不着 BigDecimal;
当前价格:这个不一样了,为了减少 double 参与金钱计算,统一使用 BigDecimal 代替带有小数的金额;
其他2:关于 Mysql 中如何选用这两种类型
首先与 java 不同的是 mysql 是用来持久化数据的,而 java 中使用的数据一般更多的是过一下内存;
数据库都要除了指定数据类型指外还需要指定精度,因此在 DB 中 Double 计算时精度的丢失比 Java 高得多;
因为 Java 默认精确到 15-16 位了;
- 更改数据类型的成本,Mysql 比 Java 代码要难得多;
考虑到以上与 java 中不同几点,做点个人使用总结:
与商业金融相关字段要使用 Decimal 来表示,如金额,费率等字段;
参与各类计算如加,减,乘,除,sum,avg 等等,也要使用 Decimal;
经纬度,可以使用 double 来表示,这个可参考 Java,只要保证精度范围即可;
如果确实不确定使用什么 double 或 Decimal 哪种类型合适,那最好使用 Decimal,毕竟稳定,安全高于一切;
注:阿里的编码规范中强调统一带小数的类型一律使用 Decimal 类型,也是有道理的,使用Decimal 可以大大减少计算踩坑的概率。