超全面!Java核心知识总结(点击查看)
超全面!Java核心知识总结(点击查看)
大家好,我是胖虎。前几天群内有个小伙伴去面试,在做笔试题的时候在这里掉坑里了。
float a1 = 0.1f;
double a2 = 0.1;
System.out.println((a1 - a2) == 0.0);
float b1 = 0.125f;
double b2 = 0.125;
System.out.println((b1 - b2) == 0.0);
double c1 = 0.625;
double c2 = 0.5;
double c3 = 0.375;
System.out.println((c1 - c2) == (c2 - c3));
double d1 = 0.8;
double d2 = 0.6;
double d3 = 0.4;
System.out.println((d1 - d2) == (d2 - d3));
大家可以先考虑一下最终的输出结果是怎么样的?
正确的答案为
false
true
true
false
这是因为在处理浮点数的时候如果操作不当很容易造成精度丢失,我们可以模拟一个发红包的场景。
一个红包中有0.5元,4个人每人从里面拿走了0.1元。
不要问我为啥钱这么少,因为我...
用代码来输出还剩多少钱。。。
double d1 = .5;
for (int i = 0; i 4; i++) {
d1 -= .1;
}
// 输出余额
System.out.println(d1);
非常简单的一个减法运算,但是执行之后却发现,输出的结果并不是
0.1
,而是
0.10000000000000003
这就是我们常见的精度丢失问题,并不是Java语言才有这种的问题,很多语言都会遇到这样的问题。这是因为他们都遵循了
IEEE 754
标准,即:
IEEE二进制浮点数算术标准(IEEE 754)
。
编码方式是符号+阶码+尾数,如图:
img
比如 float 类型来具体,我们都知道它占用 32 位,单精度浮点表示法如下:
-
符号位(sign)占用 1 位,用来表示正负数,0 表示正数,1 表示负数
-
指数位(exponent)占用 8 位,用来表示指数,实际要加上偏移量
-
小数位(fraction)占用 23 位,用来表示小数,不足位数补 0
由此可以看出,指数位决定了代销范围,小数位决定了它的精度。当在计算机中进行计算时,十进制转化为二进制表达式后,得到的尾数会很长很长。
而实际显示的尾数十倍截取或执行舍入后的近似值。这样就解释清楚了浮点数计算不准确的问题。
同理由于上述原因的存在,在对浮点数进行比较的时候非常不建议使用
==
,因为精度问题,往往会返回不符合与其的结果。当然也不能使用包装类型的
equals
double d1 = .1 * 3;
double d2 = .3;
System.out.println(d1 == d2);
上面的例子中,按照正常的逻辑来说
.1 * 3
结果是
0.3
。所以最终的结果应该是
True
。
然而执行之后结果确实
False
,d1最终的值并不是
0.3
而是
0.30000000000000004
。
问题的原因我们已经知道了,那遇到浮点数的时候该
如何正确的进行比较呢?
常用的方法有两种
1、规定误差区间
虽然我们无法做到精确的比较,但是我们可以确定一个误差范围,只要小于这个误差范围,我们就可以它们是相等的。
final double threshold = .00001;
double d1 = .1 * 3;
double d2 = .3;
if (Math.abs(d1 - d2) System.out.println("d1 = d2");
} else {
System.out.println("d1 != d2");
}
通过设定一个阈值
threshold
,然后对两个变量求绝对值,如果差值小于这个阈值,则默认相等.