java坑之浮点数运算精度丢失

举报
object 发表于 2024/02/23 17:05:11 2024/02/23
【摘要】 在写代码的过程中,一些简单的浮点数运算,我们可能直接就用double、float直接就运算了,但是这样往往可能会伴随精度丢失的风险。public static void main(String[] args) { double a = 0.3; double b = 0.2; System.out.println(a-b);}执行上诉代码块,预期结果,我们将会得到0.1,而...

在写代码的过程中,一些简单的浮点数运算,我们可能直接就用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);

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。