Java语言在处理浮点数,其实现逻辑与整数不同,如果使用不当可能会造成精度丢失、计算不准确、死循环等问题,严重的话,会造成经济损失。本文将从浮点数精度丢失入手,详细介绍下浮点数的原理及使用。
为什么会出现精度丢失计算机使用二进制存储数据,由于二进制自身局限性,导致其无法精确的表示所有小数。而浮点数是由整数部分和小数部分组成,这也就意味着,计算机无法精确表示浮点数。也即,在计算机中,浮点数存在精度丢失的问题。这里将以十进制小数转为二进制数为例,介绍下为何二进制无法精确表示小数。
十进制小数转换成二进制小数采用"乘2取整,顺序排列"的做法。基本思想如下:用二乘以当前十进制小数,然后将乘积的整数部分取出,如果乘积中的小数部分为零或者达到所要求的精度则停止计算。否则再用二乘以余下的小数部分,又得到一个乘积,再将积的整数部分取出,如此进行。完成计算后,把每次执行取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位。这里介绍下如何把十进制小数0.8125转换为二进制小数。
当然,也存在无法准确适用二进制表示的小数。如十进制小数0.7。
对于无法用二进制表示的小数,只能根据精度近似的表示。
与整数存储数值不同,浮点数在计算机的存储时,会拆分成是三个部分:符号位、指数位、尾数位。其抽象公式是:
(
−
1
)
S
∗
(
1.
M
.
.
.
)
∗
2
E
(-1)^S*(1.M...)*2^E
(−1)S∗(1.M...)∗2E
其中,
S
S
S表示符号(正数、负数)、E表示指数、M表示尾数。在Java中,float是32位存储,double是64存储,各部分的存储长度是:
因为指数位影响数的大小,指数位决定大小范围。而小数位则决定计算精度,小数位能表示的数越大,则能计算的精度越大。
float 的小数位只有 23 位,即二进制的 23 位,能表示的最大的十进制数为 2 的 23 次方,即 8388608,即十进制的 7 位,严格点,精度只能百分百保证十进制的 6 位运算。
double 的小数位有 52 位,对应十进制最大值为 4 503 599 627 370 496,这个数有 16 位,所以计算精度只能百分百保证十进制的 15 位运算。
既然浮点数不能精确表示小数,那么在执行浮点数 *** 作时(算数运算、比较运算等),要考虑精度丢失可能带来的问题。这里以浮点数比较为例,介绍两个浮点数比较带来的死循环问题。
public void createEndlessLoop() { double a = 1.6; double b = 0.3; double c = a + b; double d = 1.9; while (c != d) { System.out.println("c: " + c + ", d: " + d); } System.out.print("c == d"); }
执行上述方法,该方法将进入到死循环。浮点数的比较,由于精度丢失问题,其比较结果常常与预期相左。一种基本的思路是引入误差来辅助浮点数比较,但是这种做法仍不能满足某些场景。更多该思路的拓展可以参考链接。
如何避免精度丢失那么如何避免浮点数的精度丢失问题。其实这个问题没有银d。要根据不同的业务场景,选择合适的处理方式。如果可以不对浮点数进行运算,则尽量不使用浮点数进行运算。如果必须使用浮点数进行运算,要根据场景,选择合适的处理方式。如浮点数四则运算、比较运算场景,使用BigDecimal。上一个问题的正确处理方式如下:
public void createEndlessLoopWithBigDecimal() { BigDecimal a = new BigDecimal("1.6"); BigDecimal b = new BigDecimal("0.3"); BigDecimal c = a.add(b); BigDecimal d = new BigDecimal("1.9"); while (c.doublevalue() != d.doublevalue()) { System.out.println("c: " + c.doublevalue() + ", d: " + d.doublevalue()); } System.out.print("c == d"); }
注意,这里并没有使用形如"BigDecimal a = new BigDecimal(1.6);"的方式初始化BigDecimal实例,这是因为浮点数无法精确表示,所以使用BigDecimal(Double)创建的BigDecimal是可能损失了精度,其赋值结果可能和实际有差异。考虑下面的代码,将进入死循环。
public void createEndlessLoopWithBigDecimal() { BigDecimal a = new BigDecimal(1.6); BigDecimal b = new BigDecimal(0.3); BigDecimal c = a.add(b); BigDecimal d = new BigDecimal(1.9); while (c.doublevalue() != d.doublevalue()) { System.out.println("c: " + c.doublevalue() + ", d: " + d.doublevalue()); } System.out.print("c == d"); }
更多BigDecimal使用上的坑,可参考链接。
参考https://www.cnblogs.com/fuzongle/p/14986543.html Java浮点数计算精度丢失与解决方案
https://www.cnblogs.com/xujishou/p/7491932.html java中double和float精度丢失问题
https://www.runoob.com/w3cnote/decimal-decimals-are-converted-to-binary-fractions.html 十进制小数转化为二进制小数
https://blog.csdn.net/qq_36915078/article/details/106019023 浮点数如何转二进制
https://www.runoob.com/w3cnote/java-the-different-float-double.html Java 浮点类型 float 和 double 的主要区别
https://www.cnblogs.com/PrimoPrimo/archive/2013/02/22/3307196.html 浮点数的比较
https://developer.aliyun.com/article/785039 BigDecimal使用避坑
原创不易,如果本文对您有帮助,欢迎关注我,谢谢 ~_~
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)