Java踩坑记系列之BigDecimal
在java.math
包中提供了对大数字的操作类,用于进行高精确计算,如BigInteger
,BigDecimal
类。而平常我们开发中使用最多的float和double只能适用于一般的科学和工程计算,如果要在比较精确的计算方面如货币,那么使用float和double会相应的丢失精度,因此用于精密计算大数字的类BigDecimal
就必不可少了。所以BigDecimal
适合商业计算场景,用来对超过16位有效位的数进行精确的运算。但是BigDecimal的使用并不像float和double那样,使用不当造成的后果更严重,下面就来看下我们项目中踩过BigDecimal
的坑:
一. BigDecimal的初始化精度丢失问题
先来看下面代码的运行结果:
BigDecimal bd1 = new BigDecimal(0.1); System.out.println("bd1="+bd1); BigDecimal bd2 = new BigDecimal("0.1"); System.out.println("bd2="+bd2); BigDecimal bd3 = BigDecimal.valueOf(0.1); System.out.println("bd3="+bd3);
输出结果:
bd1=0.1000000000000000055511151231257827021181583404541015625 bd2=0.1 bd3=0.1
如果是float或double类型转Bigdecimal
,不要使用new BigDecimal()
转, 使用valueOf()
方法 或new BigDecimal("")
转成string,否则有可能出现精度问题。
《Effective Java》这本书里说过:
如果需要精确的答案,请避免使用float和double
因为float和double执行的是二进制浮点运算,二进制有些情况下不能准确的表示一个小数,就像十进制不能准确的表示1/3(1/3=0.3333...)
也就是说二进制表示小数的时候只能够表示能够用1/(2^n)的和的任意组合,例如:
-
0.5能够表示,因为它可以表示成为1/2
-
0.75也能够表示,因为它可以表示成为1/2+1/(2^2)
-
0.875也能够表示,因为它可以表示成为1/2+1/(2^2)+1/(2^3)
但是0.1不能够精确表示,因为它不能够表示成为1/(2^n)的和的形式
System.out.println(0.5*3); System.out.println(0.1*3);
大家可以本地执行下这两行代码,看下输出结果就知道为什么二进制不能表示0.1却可以表示0.5了。所以其实不是BigDecimal
的问题,BigDecimal
就是为了满足精确运算存在的,问题出在0.1它本身就一个不准确的值,这其实跟BigDecimal
无关,但在使用的时候需要注意用法。
二. BigDecimal在进行除法运算时需设置精度,否则对于除不尽的情况会抛出异常
继续看下面的代码执行结果:
BigDecimal bd4 = new BigDecimal("10"); BigDecimal bd5 = new BigDecimal("3"); System.out.println(bd4.divide(bd5));
输出结果:
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. at java.math.BigDecimal.divide(BigDecimal.java:1690) at BigDecimalTest.main(BigDecimalTest.java:38)
应该向下面这样设置小数点后的位数,以及超出后是四舍五入和向上/向下取整或者直接舍弃:
System.out.println(bd4.divide(bd5,2,BigDecimal.ROUND_DOWN));
第二个参数表示小数位数,第三个参数表示超出的位数直接舍弃(当然也可以设置四舍五入,向上取整等)
三. 不要使用BigDecimal的equals方法比较大小, 否则可能会因为精度问题导致比较结果和预期的不一致
BigDecimal bd1 = new BigDecimal("0"); BigDecimal bd2 = new BigDecimal("0.0"); System.out.println(bd1.equals(bd2)); System.out.println(bd1.[compareTo](http://javakk.com/tag/compareto "查看更多关于 compareTo 的文章")(bd2) == 0)
输出结果:
equals:false compareTo:true
如果你无法确定你的BigDecimal
值有小数情况,最好用compareTo
!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
使用加权轮询算法和 Go 实现 HTTP 负载分发代理
最近看到了一篇文章,细讲了各种分布式调度原理,其中加权轮询算法(Weighted Round-Robin)应该是离我们最近的一种方式了,Nginx 的 Upstream 就是用的这个算法,这个算法可以根据权重使得每个服务器能够均匀的负载请求,本篇主要就是来总结下使用这个算法以及 Go 内置的方法来实现一个简单的加权轮询的 HTTP 负载分发代理,并对负载分发及路由做一些延伸思考。 本篇主要从以下两个方面进行展开: 使用 Go 实现一个反向代理 使用 WRR 算法实现此反向代理的加权轮询 什么是代理 代理一般情况下我们是分为正向代理和反向代理两种形式 正向代理一般是配置在客户端,客户端需要知道代理的地址并自行配置 反向代理一般情况下对客户端是透明的,主要是配置在服务器上,大部分我们访问的 Web 应用都是通过反向代理进行配置的,这块主要是 Nginx 和 Apache 提供的功能,比如 Nginx: location / { proxy_pass: http://127.0.0.1:3000 } 还有一个简单区分的方式,我们可以看这个代理的作用,如果已知转发的后端,那这个应该是反向代理,...
- 下一篇
鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么? | 给HarmonyOS源码加上中文注释 | v16.02
鸿蒙内核源码注释中文版 【 Gitee仓|CSDN仓|Github仓|Coding仓 】项目中文注解鸿蒙官方内核源码,详细阐述鸿蒙架构和代码设计细节,精读内核源码最大的好处是:将孤立知识点织成一张高浓度,高密度底层网,对计算机体系化理解形成永久记忆,从此高屋建瓴分析/解决问题. 鸿蒙源码分析系列篇 【 CSDN| OSCHINA| WIKI 】从 HarmonyOS 架构层视角整理成文, 并首创用生活场景讲故事的方式试图去解构内核,一窥究竟。 鸿蒙虚拟内存全景图 再看鸿蒙用户空间全景图 以上两图是笔者阅读完鸿蒙内核源码内存模块所绘制,给鸿蒙内核源码逐行加上中文注释 【 Gitee仓|CSDN仓|Github仓|Coding仓 】已正式上线,四大码仓每日同步更新。更多图在仓库中用@note_pic搜索查看。 内存在内核的比重极大 内存模块占了鸿蒙内核约15%代码量,近50个文件,非常复杂。想想也是,内存何等重要,内核本身和应用程序一样,也是程序,也要在内存中运行, 大家都在一个窝里吃饭,你凭什么就管我了,凭什么的问题会在系列篇的最后结尾 鸿蒙内核源码分析(主奴机制篇) 中详细介绍, 内核...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS8编译安装MySQL8.0.19
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题