工作中遇到小数点加减乘除,double或float为何丢精度及BigDecimal咋解决?

在工作当中,提到有小数点的加减乘除运算,就会想到借助BigDecimal来处理,然而不少人对double或者float为何会丢失精度感到十分困惑 。另外,BigDecimal是怎样解决这个问题的呢 ?不多说了,我们开始 。

工作中遇到小数点加减乘除,double或float为何丢精度及BigDecimal咋解决?

1.浮点数是啥?

浮点数是计算机用于表示小数的数据类型,采用科学计数法。在java里,double是双精度、64位的浮点数,默认值是0.0d。float是单精度、32位的浮点数,默认值是0.0f;

在内存中存储

float由符号位(1bit)、指数(8bit)、尾数(23bit)组成

双精度浮点数由符号位(占1位)、指数(占11位)、尾数(占52位)组成 。

float在内存中占8位,阶码实际存储的是指数的移码,假设指数的真值是e,阶码为E,那么E=e+(2^n-1 -1),2^n-1 -1是IEEE754标准规定的指数偏移量,依据这个公式我们能得出2^8 -1=127 。于是,float的指数范围是-128到+127,double的指数范围是-1024到+1023。负指数决定了浮点数所能表达的绝对值最小的非零数,正指数决定了浮点数所能表达的绝对值最大的数,也就决定了浮点数的取值范围。

loat的范围是-2的128次方到正2的127次方,也就是说,是-3.40乘以10的38次方到正3.40乘以10的38次方;

double的范围是-2的1024次方到正2的1023次方,也就是说,范围是-1.79乘以10的308次方到正1.79乘以10的308次方 。

2.走进失真之科学计数法

我们先讲讲科学计数法,它是一种简化计数的办法,能用来近似表示极大或极小且位数较多的数,对于位数较小的数值,科学计数法没什么优势,然而对于位数较多的数值,其计数方法的优势就很显著了。比如,光的速度是300000000米/秒,全世界人口数约为6100000000 。像光的速度和世界人口数这种大数值的数,读起来不方便,写起来也不方便,因此光的速度能够写成3*10^8,全世界人口数能够写成6.1*10^9,所以计算器用科学计数法表示光速是3E8,世界人口数大约是6.1E9 。

小时候我们玩计算器时,喜欢疯狂地进行累加或者累减操作。到最后,计算器就会显示出下图的内容。而这,便是科学计数法所显示的结果 。

图中的真实值是-4.86乘以10的11次方,结果为-486000000000 。十进制科学计数法有要求,有效数字的整数部分必须在【1,9】这个区间内 。

3.走进失真之精度

计算机处理数据时会涉及数据转换和各种复杂运算,例如不同单位的换算,像不同进制(如二进制、十进制)之间的换算等。很多除法运算无法除尽,比如10÷3 = 3.3333..... 是无穷无尽的。而且精度是有限的,3.3333333乘以3并不等于10。经过复杂处理后得到的十进制数据并不精确,精度越高就越精确。float和double的精度由尾数的位数决定,其整数部分始终是一个隐含的“1”,因为它不变,所以不会对精度造成影响。float的2^23等于8388608,一共七位,由于最左为1的一位省略了,这意味着最多能表示8位数,28388608等于16777216 。有8位有效数字,不过绝对能保证的是7位,也就是说float的精度是7到8位有效数字,double:2的52次方等于4503599627370496,总共16位,同样的道理,double的精度是16到17位。

当到达一定值时会自动开始使用科学计数法,会保留相关精度的有效数字,结果是个近似数,指数为整数。在十进制中,有些小数无法完整用二进制表示,只能用有限位来表示,因此在存储时可能会有误差。将十进制的小数转化为二进制,采用乘2取整法来计算,去掉整数部分后,剩余的小数继续乘以2,一直到小数部分全部为0 。

如遇到

输出是 0.19999999999999998
对于double类型中0.3减去0.1这种情况,要把0.3转换为二进制之后再进行运算。
0.3乘以2等于0.6,等于>.0,(.6)取0后剩下0.6 。
0.6乘以2等于1.2,得出.01,在(.2)中取1,剩余0.2 。
0.2乘以2等于0.4,得出结果为0.010,其中取0,剩余0.4 。
0.4乘以2等于0.8,得到结果为.0100(.8),取0后剩余0.8 。
0.8乘以2等于1.6,等于>.01001,取1后剩余0.6 。
.............

3.总结

从上面看,能很清楚地明白浮点数为何存在精度问题。简单来讲,float和double类型主要是针对科学计算与工程计算设计的。它们执行二进制浮点运算,这些运算是经过精心设计的。其目的是在广泛的数值范围内提供更精确的快速近似和计算。然而,它们无法提供完全准确的结果。所以不能用于需要计算精确结果的场景中。浮点数达到一定大数时会自动采用科学计数法,这样的表示只是对真实数的近似,并不等于真实数,十进制小数转换为二进制时,也会出现无限循环或超出浮点数尾部长度的情况。

4.那我们怎么用BigDecimal来解决?

大家看下面的两个输出

输出结果:

你提供的内容并不是一个完整的句子,它看起来像是一个数值,请提供正确的句子以便我进行改写。

0.3

图上阿里的代码约束插件在图表上标记了警告,这使得我使用String字符串参数的构造方法创建BigDecimal。因为double无法精确表示为0.3(任何有限长度的二进制),所以用double构造函数传递的值不完全等于0.3 。使用bigdecimal时,要通过String字符串参数构造方法来创建它。对此,有没有好奇的疑问。BigDecimal的原理是什么。为何它就没事。其原理很简单。BigDecimal是不可变的。它可用来表示任意精度的带符号十进制数。double存在的问题是,从小数点转换到二进制时会丢失精度,二进制也会丢失精度。BigDecimal在处理时,会把十进制小数扩大N倍,使其在整数上进行计算,同时保留相应的精度信息。至于BigDecimal是如何保存的,可翻阅一下源代码。

5.总结

 (1)商业计算使用BigDecimal。
(2)尽量使用参数类型为String的构造函数。
BigDecimal都是不可变的,进行每一步运算时,都会产生一个新的对象,因此做加减乘除运算时,一定要保存操作后的数值。
我们常常容易忽视JDK底层的某些实现细节,进而导致出现错误,所以需要格外留意 。

来都来了,走啥走,留个言呗~

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

27阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

25阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

26阅读