血的教训,BigDecimal踩过的坑

很多人都用过Java的BigDecimal类型,但是很多人都用错了。如果使用不当,可能会造成非常致命的线上问题,因为这涉及到金额等数据的计算精度。

首先说一下,一般对于不需要特别高精度的计算,我们使用double或float类型就可以了。

由于计算机天生的无法表达完整的二进制浮点数的小数,二进制的小数是无限循环的,所以只能无限接近于精确值,这就造成了浮点计算的精度问题。此时就需要使用BigDecimal类型了。

踩坑一:初始化

关于浮点数精度的问题,我们可以看下这个代码。

打印结果会是0.2吗?不是,打印结果是0.19999999。因为b最大化接近于0.8,可能是0.80000001,近似于0.8。这就是为什么说精度要求不高时可以用double或float类型,一旦涉及到金额就不能使用浮点类型的原因。

知道了这个原理,我们使用BigDecimal能否避免浮点计算的精度问题?看下这个代码。

打印结果如下:

使用BigDecimal构造函数和使用valueOf方法初始化,两种方式得到的结果不一样,valueOf方法初始化的BigDecimal数据计算是精确的。我们看下源码就能明白了。

valueOf方法会把浮点数先转换成字符串,再用BigDecimal构造函数初始化。所以就不存在精度问题了。当然,这里要特别注意的是,valueOf方法对double类型的值可以保证精度,但是如果传的是float类型,例如0.8f,则依然会有精度问题。

踩坑结论:

  • 使用BigDecimal构造函数时,传字符串而不要传浮点类型。
  • 尽量使用valueOf方法初始化,并且不要传float类型数据。

踩坑二:比较大小

两个BigDecimal值比较是否相等,是使用equals方法还是使用compareTo方法?先来看个例子。

打印结果:

equals判断a和b不相等,compareTo判断a和b相等。我们看下equals的源码。

equals除了比较值的大小,还会比较值的精度。

踩坑结论:

  • 比较两个BigDecimal值的大小,使用compareTo方法。
  • 比较大小且限制精度,使用equals方法。

踩坑三:除法设置精度

在进行BigDecimal计算特别是除法计算时,很多人会忘记设置精度和舍入模式,最终结果就是程序会报一个ArithmeticException异常。先看示例。

报错信息如下图

官方文档是这样解释的:

"If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations."

翻译过来的意思是:

"如果商具有非终止的十进制展开,并且指定运算返回精确的结果,则引发ArithmeticException。否则,将返回除法的确切结果,就像对其他操作所做的那样。"

用人话解释上述文字,意思就是如果divide方法计算得到的商是一个无限小数,而代码预期得到一个精确数字,那么就会抛出ArithmeticException异常。

正确的使用divide代码如下。

打印结果:

设置了c的精度为2,RoundingMode.HALF_UP是向上舍入模式,即四舍五入。

踩坑结论:使用BigDecimal进行计算时,结果一定要设置精度和舍入模式。

踩坑四:字符串转换

想将BigDecimal类型转成字符串,用toString方法?先来看个示例。

打印结果:

可以看出,toString方法将BigDecimal的值转成了科学计数法的值。如果想要转成正常的数值应该使用什么方法呢?我们可以先看下BigDecimal三种转字符串的方法。

  • toString():如果需要指数,则使用科学计数法。
  • toPlainString():不带指数的字符串表现形式。
  • toEngineeringString():如果需要指数,则使用工程计数法。

踩坑结论:结合具体业务选择适用的字符串转换方法。

最后,关于数值格式化的功能,我们可以利用NumberFormat类,对BigDecimal进行格式化控制。代码示例如下。

打印结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/267537.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker-compose的介绍与使用

一、docker-compose 常用命令和指令 1. 概要 默认的模板文件是 docker-compose.yml,其中定义的每个服务可以通过 image 指令指定镜像或 build 指令(需要 Dockerfile)来自动构建。 注意如果使用 build 指令,在 Dockerfile 中设置…

7 Linux 内核移植

一、编译 ST 的 Linux 系统 1. 压缩源码 首先先下载 ST 官方源码,之前章节已经下载过了,直接输入以下命令: cd linux/atk-mpl/stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24/sources/arm-ostl-linux-gnueabi/linux-stm32mp-5.4.31-r0/ 然…

Linux命令——进程管理

进程管理 一、进程基本介绍二、进程相关命令1、ps2、kill、killall3、top4、pstree 总结 博主最近项目上线,操作了linux系统,感觉命令很容易遗忘,因此总结一下,本文记录的是linux中相关的进程管理命令 一、进程基本介绍 linux中…

java的long类型超过9位报错:the literal 987654321000 of type int is out of range

java的long类型超过9位报错 1、报错提示2、报错截图3、解决办法4、参考文章 1、报错提示 the literal 987654321000 of type int is out of range 2、报错截图 3、解决办法 long类型是一种用于表示较大整数的数据类型,范围比int类型更广泛。然而,即使…

系统延时函数的实现

滴答定时器的工作原理 STM32F103的内核时钟由AHB总线时钟(72M)经过8分频得到,即72/89M LOAD的取值范围是0-1677215,也就是VAL最多可以计2^24次。 滴答定时器的寄存器 系统延时函数的配置 当需要计1us(1/100 0000)时,9M的时钟就要计9次&#x…

Blender学习:走路机器人,骨骼绑定

文章目录 建模骨骼创建骨骼绑定 教程地址:八个案例教程带你从0到1入门blender【已完结】 建模 1 做头:新建立方体,Ctrl2细分并应用,进入编辑模式,删除一半点,然后添加镜像修改器,开启范围限制…

界面控件DevExpress WPF导航组件,助力升级应用程序用户体验!(下)

DevExpress WPF的Side Navigation(侧边导航)、TreeView、导航面板组件能帮助开发者在WPF项目中添加Windows样式的资源管理器栏或Outlook NavBar(导航栏),DevExpress WPF NavBar和Accordion控件包含了许多开发人员友好的…

多域名和通配符SSL证书的区别

域名SSL证书和通配符SSL证书都是SSL数字证书中用一张证书保护多个域名站点的证书产品,这两种类型的SSL数字证书各自有各自的特色,今天就随SSL盾小编了解多域名SSL证书和通配符SSL证书的区别。 1.保护的域名类型不同:多域名SSL证书默认保护3-…

第二证券:强化数字引领,助力跨境电商阳光化发展

12月5日,交通银行深圳分行成功在深圳跨境阳光服务途径落地了跨境电商收汇事务,成为首家与该途径对接跨境电商阳光收款服务的银行。与深圳跨境阳光服务途径的成功对接,是深圳交行深化推动数字化转型,提高金融科技价值创造才干的重要…

vue自定义指令及常用的自定义指令封装

vue2 自定义指令 官网链接https://v2.cn.vuejs.org/v2/guide/custom-directive.html 指令注册 这里是一个 Vue2 的指令合集,详细的指令移步下面具体的指令文章,现在我们在这里要介绍如何在项目中统一管理和使用这些指令。 注册指令 单文件引入注册 …

基于深度学习的yolov5入侵检测系统

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介IntroductionYOLOv5 Overview入侵检测系统架构1. 数据采集2. YOLOv5模型训练3. 实时监测4. 告警与反馈 性能评估与优化 二、功能三、系统四. 总结 一项目简…