浮点数不精确
计算机内部无法用二进制的小数来精确的表达。
public class Tesz {
public static void main(String[] args) {
double a = 0.1;
float c = 0.1f;
System.out.println(a==c);//输出false
}
}
- 如果运算符任意一方的类型为double,则另一方会转换为double
- 否则,如果运算符任意一方的类型为float,则另一方会转换为float
- 否则,如果运算符任意一方的类型为long,则另一方会转换为long
- 否则,两边都会转换为int
根据IEEE 754,单精度的float是32位,双精度的double为64位
第一部分(s)为符号位,第二部分(exponent)为指数位,第三部分(mantissa)为基数部分。 这是科学计数法的二进制表示。
- 比如像3.1415926.。。。 或者无限无限循环小数,就会将不能标识的部分舍掉。
- 二进制无法精确表示浮点数
二进制为什么无法精确表示浮点数
单精度浮点数0.1表示成二进制
System.out.println(Integer.toBinaryString(Float.floatToIntBits(0.1f)));
结果是:111101110011001100110011001101
双精度的浮点数0.1的二进制
System.out.println(Long.toBinaryString(Double.doubleToLongBits(0.1d)));
结果是:11111110111001100110011001100110011001100110011001100110011010
float转换后的double的值已经和直接赋值的double的值比较
System.out.println(Long.toBinaryString(Double.doubleToLongBits(0.1f)));
结果是:11111110111001100110011001100110100000000000000000000000000000
很明显不一样
二进制小数的表达形式
根据国际标准IEEE 754,任意一个二进制浮点数V可以表示成下面的形式:
(1)(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
(2)M表示有效数字,大于等于1,小于2。
(3)2^E表示指数位。
举个例子 用二进制表达十进制的 0.2
0.01 = 1/4 = 0.25 ,太大
0.001 =1/8 = 0.125 , 又太小
0.0011 = 1/8 + 1/16 = 0.1875 , 逼近0.2了
0.00111 = 1/8 + 1/16 + 1/32 = 0.21875 , 又大了
0.001101 = 1/8+ 1/16 + 1/64 = 0.203125 还是大
0.0011001 = 1/8 + 1/16 + 1/128 = 0.1953125 这结果不错
0.00110011 = 1/8+1/16+1/128+1/256 = 0.19921875
.......................................................
第一种方法:转换成字符串
如果要比较的两个浮点数数据的字符串精度相等,可以将数据转换成string然后借助string的equals方法来间接实现比较两个double数据是否相等。注意这种方法只适用于比较精度相同的数据,并且是只用用于比较是否相等的情况下,不能用来判断大小。
Float.toString(453.2348f).equals(Float.toString(0.342f))
Double.toString(0.8456d).equals(Float.toString(0.242f))
第二种方法:使用sun提供的Double.doubleToLongBits()方法
该方法可以将double转换成long型数据,从而可以使double按照long的方法(<, >, ==)判断是否大小和是否相等。
Double.doubleToLongBits(0.01) == Double.doubleToLongBits(0.01)
Double.doubleToLongBits(0.02) > Double.doubleToLongBits(0.01)
Double.doubleToLongBits(0.02) < Double.doubleToLongBits(0.01)
第三种方法:误差内比较
对于double类型,比如double d1=0.0000001,double d2=0d 当判断两个数据d1和d2是否相等的时候,一般不直接使用
if(d1==d2)
...
第四种办法:BigDecimal类型
double a = 0.001;
double b = 0.0011;
BigDecimal data1 = new BigDecimal(a);
BigDecimal data2 = new BigDecimal(b);
data1.compareTo(data2)
非整型数,运算由于精度问题,可能会有误差,建议使用BigDecimal类型!
反例1:用equals方法可以吗
Double a = Double.valueOf("0.0");
Double b = Double.valueOf("-0.0");
System.out.println(a.equals(b));
false
Double a = Math.sqrt(-1.0);
Double b = 0.0d / 0.0d;
Double c = a + 200.0d;
Double d = b + 1.0d;
System.out.println(a.equals(b));
System.out.println(b.equals(c));
System.out.println(c.equals(d));
true
true
true
equals方法是比较2个对象是否等值,而不是对象的值是否相等
反例2:用compareTo方法可以吗
public static void main(String[] args) {
Double a = Double.valueOf("0.0");
Double b = Double.valueOf("-0.0");
System.out.println(a.compareTo(b));
//1
Double a1 = Math.sqrt(-1.0);
Double b1 = 0.0d / 0.0d;
Double c1 = a1 + 200.0d;
Double d1 = b1 + 1.0d;
System.out.println(a1.compareTo(b1));
System.out.println(b1.compareTo(c1));
System.out.println(c1.compareTo(d1));
//0
//0
//0
}
a和b表示为NaN(Not a Number) ,并不是数字,无法比较。
总结
在进行浮点数比较的时候,主要需要考虑3个因素
- NaN
- 无穷大/无穷小
- 舍入误差
所以,要比较浮点数是否相等,需要做的事情是:
- 排除NaN和无穷
- 在精度范围内进行比较
参考连接
https://blog.csdn.net/renwotao2009/article/details/51637163
http://en.wikipedia.org/wiki/Floating_point
https://blog.csdn.net/wcxiaoych/article/details/42806313#
https://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html
本公众号分享自己从程序员小白到经历春招秋招斩获10几个offer的面试笔试经验,其中包括【Java】、【操作系统】、【计算机网络】、【设计模式】、【数据结构与算法】、【大厂面经】、【数据库】期待你加入!!!