首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一律使用 BigDecimal,避免后患?

一律使用 BigDecimal,避免后患?

作者头像
业余草
发布2020-11-13 14:56:23
1.6K0
发布2020-11-13 14:56:23
举报
文章被收录于专栏:业余草业余草

你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

zhuanlan.zhihu.com/p/94144867

推荐:https://www.xttblog.com/?p=5116

一律使用 BigDecimal,避免后患?

一、背景

总在项目中看到 Double 与 BigDecimal 被用错的情况,竟然有人告诉我:“一律使用 BigDecimal,避免后患”,我相信这位兄弟肯定是被精度问题搞蒙了,因此我想同步一下我的使用姿势,仅提供参考。

二、两种类型使用过程中都有可能出坑

  1. double 的计算时容易出现不精确的问题

double 的小数部分容易出现使用二进制无法准确表示。

如十进制的0.1,0.2,0.3,0.4 都不能准确表示成二进制。

具体原因相信大家都懂,我就不多说了。如果有不懂的可以私信我或者评论留言!

  1. dobule 的 = 比较要注意

dobule 小数超过 15 位的相等比较就不一定对了。都是 true。

  1. BigDecimal 的除法除不尽会出现异常:ArithmeticException

所以,使用 BigDecimal 进行除法运算一定要有精度!

  1. 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》,此书也是非常推荐的一本经典书)

  1. 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);

总之每次修改都要重新指向新对象,才能保证计算结果是对的。

  1. BigDecimal 比较大小操作不方便,毕竟是对象操作

比较大小和相等都使用 compareTo,如果需要返回大数或小数可使用 max,min。且注意不能使用 equals。

三、效率比较

比如:1 累加到 1000000(以本人机器 MacBookPro 2018 i7 2.2G)double 比 BigDecimal 快大约 10 倍

double: 2ms

BigDecimal:16ms

四、优缺点总结

double 的优缺点:

  1. double在计算过程中容易出现丢失精度问题
  2. 使用方便,有包装类,可自动拆装箱,计算效率高

BigDecimal 的优缺点:

  1. 精度准确,但做除法时要注意除不尽的异常
  2. 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 中如何选用这两种类型

  1. 首先与 java 不同的是 mysql 是用来持久化数据的,而 java 中使用的数据一般更多的是过一下内存;
  2. 数据库都要除了指定数据类型指外还需要指定精度,因此在 DB 中 Double 计算时精度的丢失比 Java 高得多;

因为 Java 默认精确到 15-16 位了;

  1. 更改数据类型的成本,Mysql 比 Java 代码要难得多;

考虑到以上与 java 中不同几点,做点个人使用总结:

  1. 与商业金融相关字段要使用 Decimal 来表示,如金额,费率等字段;
  2. 参与各类计算如加,减,乘,除,sum,avg 等等,也要使用 Decimal;
  3. 经纬度,可以使用 double 来表示,这个可参考 Java,只要保证精度范围即可;
  4. 如果确实不确定使用什么 double 或 Decimal 哪种类型合适,那最好使用 Decimal,毕竟稳定,安全高于一切;

注:阿里的编码规范中强调统一带小数的类型一律使用 Decimal 类型,也是有道理的,使用Decimal 可以大大减少计算踩坑的概率。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-11-12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 你知道的越多,不知道的就越多,业余的像一棵小草!
  • 编辑:业余草
  • zhuanlan.zhihu.com/p/94144867
  • 推荐:https://www.xttblog.com/?p=5116
  • 一、背景
  • 三、效率比较
  • 四、优缺点总结
  • 五、使用场景推荐
  • 其他1:代码中看到碰到让我觉得有问题的地方
    • 分析
    • 其他2:关于 Mysql 中如何选用这两种类型
    相关产品与服务
    云数据库 SQL Server
    腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档