在我们日常工作中数值计算是不可避免的,特别是电商类系统中,这个问题一般情况下我们都是特别注意的,但是一不注意就会出大问题,跟钱有关的事情没小事。这不新来的大兄弟就一个不注意,在这个小阴沟里翻车了,闹笑话了。

为了我们以后可以避免在这个问题上犯错,我今天特地写了这一篇来总结一下。

避免用Double来进行运算

使用Double来计算,我们以为的算术运算和计算机计算的并不完全一直,这是因为计算机是以二进制存储数值的,我们输入的十进制数值都会转换成二进制进行计算,十进制转二进制再转换成十进制就不是原来那个十进制了,再也不是曾经那个少年了。举个例子:十进制的0.1转换成二进制是0.0 0011 0011 0011...(无数个0011),再转换成十进制就是0.1000000000000000055511151231,看到了吧,没有骗你的。

计算机无法精确地表达浮点数,这是不可避免的,这是为什么浮点数计算后精度损失的原因。

System.out.println(0.1+0.2);
System.out.println(1.0-0.8);
System.out.println(4.015*100);
System.out.println(123.3/100);

通过简单的例子,我们发现精度损失并不是很大,但是这并不代表我们可以使用,特别是电商类系统中,每天少说几百万的单量,每笔订单哪怕少计算一分钱,算下来也是一笔不小的金额,所以说,这不是个小事情,然后很多人就说,金额计算啊,你用BigDecimal啊,对的,这个没毛病,但是用了BigDecimal就完事大吉了吗?当问出这句话的时候,就说明这其中必有蹊跷。

BigDecimal你遇见过哪些坑?

还是通过一个简单的例子,计算上边例子中的运算,来看一下结果:

System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2)));
System.out.println(new BigDecimal(1.0).subtract(new BigDecimal(0.8)));
System.out.println(new BigDecimal(4.015).multiply(new BigDecimal(100)));
System.out.println(new BigDecimal(123.3).divide(new BigDecimal(100)));

我们发现使用了BigDecimal之后计算结果还是不精确,这里就要记住BigDecimal的第一个坑了:

BigDecimal来表示和计算浮点数的时候,要使用String的构造方法来初始化BigDecimal。

小的改进一下再来看看结果:

System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
System.out.println(new BigDecimal("1.0").subtract(new BigDecimal("0.8")));
System.out.println(new BigDecimal("4.015").multiply(new BigDecimal("100")));
System.out.println(new BigDecimal("123.3").divide(new BigDecimal("100")));

那么接下来一个问题,使用了BigDecimal就万事大吉了吗?并不是的!

接下来我们来看一下BigDecimal的源码,这里面有一个地方需要注意,先看图:

注意看这两个属性,scale表示小数点右边几位,precision表示精度,就是我们常说的有效长度。

前边我们已经知道,BigDecimal必须传入字符串类型数值,那么如果我们现在是一个Double类型数值,该如何操作呢?通过一个简单的测试我们来看一下:

 private static void testScale() {BigDecimal bigDecimal1 = new BigDecimal(Double.toString(100));BigDecimal bigDecimal2 = new BigDecimal(String.valueOf(100d));BigDecimal bigDecimal3 = BigDecimal.valueOf(100d);BigDecimal bigDecimal4 = new BigDecimal("100");BigDecimal bigDecimal5 = new BigDecimal(String.valueOf(100));print(bigDecimal1);print(bigDecimal2);print(bigDecimal3);print(bigDecimal4);print(bigDecimal5);
}private static void print(BigDecimal bigDecimal) {System.out.println(String.format("scale %s precision %s result %s", bigDecimal.scale(), bigDecimal.precision(), bigDecimal.multiply(new BigDecimal("1.001"))));
}

run一下我们发现,以上前三种方式是将double转换成BigDecimal之后,得到的BigDecimal的scale都是1,precision都是4,后两种方式的toString方法得到的scale都是0,precision都是3,与1.001进行乘运算后,我们发现,scale是两个数的scale相加的结果。

我们在处理浮点数的字符串的时候,应该显式的方式通过格式化表达式或者格式化工具来明确小数位数和舍入方式。

浮点数的舍入和格式化该如何选择?

我们首先来看看使用String.format的格式化舍入,会有什么结果,我们知道浮点数有double和float两种,下边我们就用这两种来举例子:

double num1 = 3.35;
float num2 = 3.35f;
System.out.println(String.format("%.1f", num1));
System.out.println(String.format("%.1f", num2));

得到的结果似乎与我们的预期有出入,其实这个问题也很好解释,double和float的精度是不同的,double的3.35相当于3.350000000000000088817841970012523233890533447265625,而float的3.35相当于3.349999904632568359375,String.format才有的又是四舍五入的方式舍入,所以精度问题和舍入方式就导致了运算结果与我们预期不同。

Formatter类中默认使用的是HALF_UP的舍入方式,如果我们需要使用其他的舍入方式来格式化,可以手动设置。

到这里我们就知道通过String.format的方式来格式化这条路坑有点多,所以,「浮点数的字符串格式化还得要使用BigDecimal来进行」

来,上代码,测试一下究竟是不是那么回事:

BigDecimal num1 = new BigDecimal("3.35");
//小数点后1位,向下舍入
BigDecimal num2 = num1.setScale(1, BigDecimal.ROUND_DOWN);
System.out.println(num2);
//小数点后1位,四舍五入
BigDecimal num3 = num1.setScale(1, BigDecimal.ROUND_HALF_UP);
System.out.println(num3);
输入结果:
3.3
3.4

这次得到的结果与我们预期一致。

BigDecimal不能使用equals方法比较?

我们都知道,包装类的比较要使用equals,而不能使用==,那么这一条在Bigdecimal中也适用吗?数据说话,简单的一个测试来说明:

System.out.println(new BigDecimal("1").equals(new BigDecimal("1.0")))
结果:false

按照我们的理解1和1.0是相等的,也应该是相等的,但是Bigdecimal的equals在比较中不只是比较了value,还比较了scale,我们前边说了scale是小数点后的位数,明显两个值的小数点后位数不一样,所以结果为false。

实际的使用中,我们常常是只希望比较两个BigDecimal的value,这里就要注意,要使用compareTo方法:

System.out.println(new BigDecimal("1").compareTo(new BigDecimal("1.0")))
结果:true

最后

再总结一下今天的文章:

  • 避免使用Double来进行运算

  • BigDecimal的初始化要使用String入参或者BigDecimal.valueOf()

  • 浮点数的格式化建议使用BigDecimal

  • 比较两个BigDecimal的value要使用compareTo

END

推荐好文

强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!分享一套基于SpringBoot和Vue的企业级中后台开源项目,代码很规范!
炫酷,SpringBoot+Echarts实现用户访问地图可视化(附源码)

BigDecimal你遇见过哪些坑?相关推荐

  1. BigDecimal的ROUND_DOWN()中的坑

    一.double类型精度缺失处理 由于计算小数,需要向下截取小数位,保留两位小数,但是用了ROUND_DOWN截取却出现了问题 public class Test {public static voi ...

  2. springcloud整合nacos,遇见一个萝卜一个坑,不小心蹲了一会儿

    一.最近研发把所有老项目全调整使用nacos,例子是有,但是还是踩了一些坑,记录下,以后再犯掌嘴. 1.首先遇到的就是版本号的问题,真的很烦.下面是一个可以用的版本号,同事试出来的,没太多时间去试更多 ...

  3. BigDecimal的使用和一些坑

    在金融领域进行业务计算时,因为要保证精度的准确,通常不建议使用float和double进行数学计算. float和double类型主要是为了科学计算和工程计算而设计的.他们执行二进制浮点运算,这是为了 ...

  4. 又遇见一个被坑的客户,新买的固态是旧的-_-||,应该是SM2246XT主控

    一客户带着一个联想的笔记本过来,说电脑动不动蓝屏死机,看看怎么回事,我开机看了下配置,I3的三代处理器,4G内存,120G固态加500G机械.系桶也是拆装不久的,客户说上个月在外地电脑城才升级了固态硬 ...

  5. 字符串转bigdecimal类型_BigDecimal你会用吗?

    点击关注"故里学Java" 右上角"设为星标"好文章不错过 在我们日常工作中数值计算是不可避免的,特别是电商类系统中,这个问题一般情况下我们都是特别注意的,但是 ...

  6. 注意了,这些数值计算的坑千万别踩!

    作者 | 故里学Java  责编 | 张文 头图 | CSDN 下载自视觉中国 来源 | 故里学Java(ID:WLQ171223) 在我们日常工作中数值计算是不可避免的,特别是电商类系统中,这个问题 ...

  7. mac gcc安装_16_超级小白Mac Pro下安装superset遇见的坑

    开始数据分析工作2个月了,Leader让我把昨天下午遇见的superset坑写个文档,就更新在这儿吧. Superset Superset是一款轻量级的BI工具,由Airbnb的数据部门开源.整个项目 ...

  8. 历时三年,美图全面容器化踩过的坑

    本文由分享演讲整理而成.通过围绕美图业务和大家分享下美图容器基础平台建设中的探索经验以及在业务落地过程中的具体问题和相应的方案.美图从2016 年开始了容器相关的探索到 2018 年业务基本实现容器化 ...

  9. swift开发的小坑

    ####swift 几个比较好的UI库 swift UI库 ###1.tableView的代理方法 在swift中代理变得更加重要,当在继承代理的时候,代理的require方法必须实现,否则直接就报错 ...

最新文章

  1. HashMap 的 7 种遍历方式与性能分析!(强烈推荐)
  2. c语言不能写入文件,求大神看看为什么不能将数据写入文件
  3. 使用饼图_【Excel饼图使用系列】之使用饼图的注意事项及几个小技巧
  4. python目录下的文件夹_Python列出当前文件夹下文件的两种方法
  5. [BZOJ 3647]
  6. jquery 判断点击次数_jquery编程开发实现点击页面计算点击次数
  7. 本期期刊主题:ASP.NET技术与JavaScript技巧,包括控件等
  8. netty 图解_Netty工作原理架构图
  9. 楼对面的男士夏天就光膀子,请问大家,男士真的都爱光膀子吗?
  10. 优秀程序员必须知道的八件事情
  11. android kill process,为什么Application有时会在killProcess上重启?
  12. Python爬虫 - 01.实现贴吧一键签到
  13. Navicat生成的.psc格式文件数据库导入
  14. 微信小程序/小游戏运行环境小结
  15. XDOJ1184 - 贪心的小白羊
  16. 【OS Pintos】Project1 项目要求说明 | 进程中止信息 | 参数传递 | 用户内存访问 | 有关项目实现的建议
  17. Bootstrap 中的 aria-label 和 aria-labelledby
  18. jquery拼接ajax 的json和字符串拼接
  19. 当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
  20. 2008年网游发展热点:教育网游产业分析

热门文章

  1. 卸任后的马云“忙”坏了,健美大赛、蹦迪、修空调、种蘑菇,真相看懵了
  2. 马云今日正式退休!卸任后的他,仍有12个身份...
  3. 小米第二款5G手机是小米9?升级版小米9 配置强悍!
  4. 苹果对其语音助手Siri进行显著改进:今秋将有7大新功能
  5. 千元内无敌!红米两款新机发布 小米9侧目?
  6. 就算边框缩窄到极致也不用刘海屏?魅族16s最新渲染图曝光
  7. 来了!小米9发布时间确定:2月20日见 为你而战
  8. 自动化测试之Appium模拟机测试
  9. 不要让“破事”、“烂人”毁了你的工作计划
  10. 前端开发中的调试技巧