大家好,我是程序员牛肉。
今天在看牛客面试题的时候,看到了这个问题:“Java的BigDecimal 为什么可以做到高精度运算?”说实话你要让我面试的时候回答我还真不一定能够回答上来,不知道你们能不能回答上来这个问题。
因此我们这篇文章就来介绍一下BigDecimal的源码实现细节吧,争取让大家能够做到“知其然,知其所以然”。
先简单说一下为什么会出现丢失精度的问题:因为当前的二进制没有办法以有限位的形式存储所有的十进制数字。
例如十进制下的“0.1”转成二进制之后是一个无限循环的小数:
而现在的浮点数在计算机系统中的表达方式基本都是遵守IEEE-754规范的。EEE-754标准规定了两种浮点数格式,分别是单精度浮点数和双精度浮点数,具体如下:
可以看到无论是单精度浮点数还是双精度浮点数都是有自己的存储上限的,例如单精度浮点数最多存储23位,双精度浮点数最多存储52位。也就是说这两个浮点数格式实际上都对真实的数组做了截取使其不超出自己的最大存储位数,并不存储真实的数字本身。由于抛弃了一部分数字,所以产生了所谓的浮点误差。
而这种浮点误差在金额这种对数字计算比较敏感的业务下是一个绝不允许出现漏洞。因此在金融领域我们常使用BigDecimal来保证数据的精度。那么BigDecimal是如何保证小数的精度的呢?
其实一句话就足以概括:BigDecimal在存储小数的时候,会把小数扩大N倍使其成为一个整数,并且保留相对应的精度信息。
在BigDecimal内部就可以看到这两个变量:
存储精度:
存储扩大n倍后的数字:
当我们尝试使用构造一个Bigdecimal对象的时候,更加推荐使用字符串小数作为构造参数:
BigDecimal bigDecimal = new BigDecimal("3.1415926");
我们可以对这段代码通过打断点的方式来查看关键信息:
通过断点可以判断Bigdecimal的运行机制符合我们上文中的描述:“BigDecimal在存储小数的时候,会把小数扩大N倍使其成为一个整数,并且保留相对应的精度信息。”
那为什么不推荐直接使用小数来作为构造参数呢?打个断点一看便知:
BigDecimal bigDecimal = new BigDecimal(3.14);
看似我们这里存放的是3.14这个小数,但是还记得我们之前说的“不是所有的小数都可以被二进制正确表达嘛?”
在存在浮点误差的情况下,事实上3.14被存储为了:
这里直接给intcompact干成负数了。原因还是因为去除掉小数点后的数字太大了,给intCompact干溢出了。
通过源码可以发现:intCompact的类型是long,也就是-9223372036854775808 到9223372036854775807。
[这里的intcompact应该是溢出了多轮。其实最近黄子韬送车的时候,直播间点赞数给干成负数也是因为这个原因:每一种类型变量的表达范围都是固定的,当超出这个范围的时候就开始循环溢出。]
基于Bigdecimal的这种方式,在比较一些数字的时候就会存在大小相同,但是精度不相同的情况。例如3.14和3.140:
3.14:
3.140:
我们可以清楚的看到:3.14的精度是2,而3.140的精度是3。而常用的比较大小的equals方法在Bigdecimal中被重写为了:
可以看到在equals中还有对精度的比较。而3.14和3.140的精度是不相同的,因此当我们只是比较数值的时候,不要使用equals方法,而是compareTo方法。
这个方法首先通过快速路径处理相同的小数位和非膨胀的情况,其次通过符号判断决定大小,最后在符号相同的情况下比较绝对值。返回的整数值表示当前对象与指定对象之间的大小关系:正值表示当前对象大,负值表示当前对象小,零表示相等。
那今天关于Bigdecimal的文章就介绍到这里了。相信通过我的介绍,你已经大致了解Java的BigDecimal是如何做到高精度运算的。希望我的文章可以帮到你。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有