java坑之浮点数运算精度丢失
在写代码的过程中,一些简单的浮点数运算,我们可能直接就用double、float直接就运算了,但是这样往往可能会伴随精度丢失的风险。
public static void main(String[] args) {
double a = 0.3;
double b = 0.2;
System.out.println(a-b);
}
执行上诉代码块,预期结果,我们将会得到0.1,而实际输出:0.09999999999999998。
造成上述的现象的原因,主要是因为无限小数的情况。这里就有点问题了,0.1不是只有一位小数吗,为什么说是无限位小数呢,这就要涉及数据的存储机制了。
在java中,我们了解到double是8个字节,也就是64位,是一个有限的长度,从java的基础知识中我们了解到,double的存储的底层是二进制数据存储,而二进制是如何存储小数的呢,我将举例说明:
#举例:17.625
#分为整数和小数
#整数的算法:
17 %2 = 8 余 1 (低位)
8%2 = 4 余0(高位)
4%2 = 2 余0(高位)
2%2 = 1 余0 (高位)
1%2= 0余1 (低位)
#整数二进制为10001
#小数的算法,于整数不相同,他是通过乘以2进行运算
0.625*2 = 1.25 -->1(低位)
0.25*2 =0.5 --> 0(高位)
0.5*2 = 1.0 --> 1 (高位)
#小数的二进制为101
上诉我得到了整位(1001)和小数位(101)。当然后续还有其他的算法,不能直接存储,后续算法我仅做简述:
将10001.101的小数移到左边只剩1,即整体右移4位。得到指数4和底数0001101。
指数处理:使用4+127得到132。132的二进制 10000011。
底数处理:保持不动0001101
符号:正数0
将符合+指数+底数这样的顺序拼接即可。
17.625 得到二进制01000001 10001101 00000000 00000000。
好跑题了,上述就是完整的过程了,不过还是回归最开始的问题,就是为何0.1的存储为什么会存在无限小数的情况,从上述的算法,我们知道小数位的算法,是通过乘以2来计算,直到得到0。
0.1 * 2 = 0.2 * 2 = 0.4 * 2 = 0.8 * 2 = 0.6 * 2 = 0.2 * 2 = 0.4....。可以看到这里已经重复了,表现出来就是无限的小数,double自然不够存储,就存在了精度丢失的问题。
如何解决?
我们常知的解决办法就是使用更高精度的算法。例如BigDecimal,但是需要注意使用的方法。举例两种写法
#直接使用值作为构参 输出0.1000000000000000055511151231257827021181583404541015625
BigDecimal num1 = new BigDecimal(0.02);
BigDecimal num2 = new BigDecimal(0.03);
System.out.println(num2.subtract(num1));
#通过转换为字符串作为构参 输出:0.1
BigDecimal num3 = new BigDecimal(Double.toString(0.02));
BigDecimal num4 = new BigDecimal(Double.toString(0.03));
System.out.println(num4.subtract(num3));
我们需要使用String的方法作为构参,才能避免精度丢失,当然BigDecimal也帮忙封装了相应的函数,实际就是转了一次, BigDecimal.valueOf()。
ok,最后猜一下,下列代码输出值多少。
double a = 0.1;
double b = 0.1000000000000000055511151231257827021181583404541015625;
double c = 0.1000000000000000095511151231257827021181583404541015625;
System.out.println(a==b);
System.out.println(a==c);
System.out.println(b==c);
- 点赞
- 收藏
- 关注作者
评论(0)