double浮点数运算为啥会丢失精度?

前言:在工作中,谈到有小数点的加减乘除都会想到用BigDecimal来解决,但是有很多人对于double或者float为啥会丢失精度一脸茫然。还有BigDecimal是怎么解决的?话不多说,我们开始。

1.浮点数是啥?

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

在内存中存储

float      符号位(1bit)   指数(8 bit)     尾数(23 bit)
double   符号位(1bit)  指数(11 bit)   尾数(52 bit)

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

float的范围为-2^128 ~ +2^127,也即-3.40E+38 ~ +3.40E+38;
double的范围为-2^1024 ~ +2^1023,也即-1.79E+308 ~ +1.79E+308

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

我们先说说科学计数法,科学计数法是一种简化计数的方法,用来近似表示一个极大或极小且位数较多的数,对于位数较小的数值,科学计数法没有什么优势,但对于位数较多的数值其计数方法的优势就非常明显了。例如:光的速速是300000000米/秒,全世界人口数大约是6100000000。类似光的速度和世界人口数这样大数值的数,读、写都很不方便,所以光的速度可以写成3*108,全世界人口数可以写成6.1*109。所以计算器用科学计数法表示光速是3E8,世界人口数大约是6.1E9。

我们小时候玩计算器喜欢疯狂的累加或者累减,到最后计算器就会显示下图。这个就是科学计数法显示的结果

那图中真实的值是  -4.86*10^11=-486000000000。十进制科学计数法要求有效数字的整数部分必须在【1,9】区间内。

3.走进失真之精度

计算机在处理数据都涉及到数据的转换和各种复杂运算,比如,不同单位换算,不同进制(如二进制十进制)换算等,很多除法运算不能除尽,比如10÷3=3.3333…无穷无尽,而精度是有限的,3.3333333x3并不等于10,经过复杂的处理后得到的十进制数据并不精确,精度越高越精确。float和double的精度是由尾数的位数来决定的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。float:2^23 = 8388608,一共七位,由于最左为1的一位省略了,这意味着最多能表示8位数: 2_8388608 = 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 => .010 (.4)取0剩0.4
0.4 * 2 = 0.8 => .0100 (.8) 取0剩0.8
0.8 * 2 = 1.6 => .01001 (.6)取1剩0.6

3.总结

看完上面,大概清楚了为啥浮点数会有精度问题。简单来说float和double类型主要是为了科学计算和工程计算而设计,他们执行二进制浮点运算,这是为了在广泛的数值范围上提供较为精确的快速近和计算而精心设计的。然而,他们并没有提供完全精确的结果,所以不应该被用于精确的结果的场合。浮点数达到一定大的数会自动使用科学计数法,这样的表示只是近似真实数而不等于真实数。当十进制小数位转换二进制的时候也会出现无限循环或者超过浮点数尾数的长度。

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

大家看下面的两个输出

输出结果:

0.299999999999999988897769753748434595763683319091796875
0.3

图上阿里的代码约束插件已经标注警告,让我使用String参数的构造方法创建BigDecimal。因为double不能精确地表示为0.3(任何有限长度的二进制),构造方法传递的值也是不完全等于0.3。大家在使用BigDecimal的时候一定要用String参数的构造方法来创建。说到这里,是木有还有好奇的宝宝有疑问,BigDecimal的原理是啥?为啥它就没有问题呢?其实原理很简单,BigDecimal是不可变的,可以用来表示任意精度的带符号十进制数。double之所以会出问题,是因为小数点转二进制丢失精度。**BigDecimal在处理的时候把十进制小数扩大N倍让它在整数上进行计算,并保留相应的精度信息。**至于BigDecimal是怎么保存的可以翻阅一下源代码。

博客地址: https://my.oschina.net/wangnian

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 是的,当使用双精度浮点数double)进行计算时,可能发生精度丢失的情况。 在计算机内部,数字通常用二进制表示。然而,有些十进制小数无法用二进制准确地表示,因此在转换过程中出现舍入误差。由于 double精度为 64 位,因此它可以表示大多数数字,但仍有一些小数无法准确表示。这可能导致在执行数学运算时出现精度丢失的情况。 例如,如果将 0.1 和 0.2 相加,得到的结果应该是 0.3。但是,由于这些数字无法准确地表示为二进制,使用 double 进行计算时可能得到一个非常接近于 0.3 的结果,而不是准确的 0.3。 因此,在需要高精度计算的情况下,应该使用专门的高精度计算库或使用其他数据类型,例如十进制浮点数或整数。 ### 回答2: double是一种浮点数类型,它在进行数值计算时可能因为精度问题而造成丢失。这是由于double类型使用有限的位数来表示一个浮点数,因此它无法精确地表示所有的实数值。 由于double类型使用二进制表示,而绝大部分的实数值在二进制中是无限循环的,因此在将实数值转化为double类型时,存在一定的误差。这个误差随着计算的累积越来越大,导致最终的结果与预期结果有所偏差。 例如,当对两个很大或很小的数进行运算时,double类型的精度问题更加明显。在这种情况下,即使两个数之间的差距很小,但由于精度问题,计算结果可能变得明显错误。 为了解决这个问题,可以使用其他精度更高的数值类型,如BigDecimal。BigDecimal使用十进制进行计算,因此能够较好地保留浮点数精度。虽然BigDecimal的计算速度相对较慢,并且对内存的消耗较大,但在需要较高精度的场景下,它是一个很好的替代选择。 总而言之,double类型的精度问题是由于浮点数在计算机中的二进制表示有限而引起的。为了避免精度丢失,特别是在对大范围或小差距的数进行计算时,可以考虑使用其他精度更高的数值类型。 ### 回答3: double是一种浮点数数据类型,用于存储十进制的小数。然而,由于计算机内部使用二进制编码来表示数字,而十进制的小数无法完全转化为二进制表示,因此在存储和计算过程中可能导致精度丢失。 在浮点数计算中,由于有限的内存空间,double类型只能精确表示有限的小数位数。当进行复杂的算术运算时,精度丢失可能积累并导致结果的偏差。 例如,计算0.1 + 0.2时,预期结果应为0.3。然而,由于0.1和0.2无法精确转化为二进制表示,计算机内部存储的近似值相加后可能产生微小的误差,导致结果为0.30000000000000004。这是因为0.1和0.2的二进制表示无限循环,而double类型只能存储有限的小数位数,因此近似值产生。 为了解决这个问题,一种方法是使用BigDecimal类来进行精确的小数计算。BigDecimal可以存储任意精度的小数,并提供了一系列的数值计算方法,避免了double类型的精度丢失问题。 总之,尽管double是一种常用的浮点数数据类型,但在进行精确的小数计算时,可能遇到精度丢失问题。为了确保计算结果的准确性,应该采用使用BigDecimal等精确计算的方法来处理小数运算。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值