在Java中,浮点类型有float、double两种类型,主要用于表示小数。我们经常看到浮点数有精度问题,不适用于比较大小或比较相等的逻辑。任何数字在计算机中都是用0和1的二进制形式来表示的,那么对于浮点数又是如何使用0和1表示出来的呢?
浮点数二进制计算方法
对于整数的二进制计算,采用“除2取余,逆序排列”来算出二进制,如下所示:
对于小数的二进制计算,采用“乘2取整,顺序排列”来算出二进制,如下所示:
浮点数二进制转10进制
要使用二进制表示浮点数,就是要让小数部分也能使用二进制来表示。二进制整数部分,最低位表示2的0次方,往高位依次增加,为2的1次方,2次方,3次方…,小数部分最高位是2的-1次方,往低位依次为2的-2次方,2的-3次方…,如果二进制为0则不做计算,如下表格所示。
二进制 | 1 | 1 | 1 | 1 | 1 | . | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|---|---|
24 | 23 | 22 | 21 | 20 | 小数点 | 2-1 | 2-2 | 2-3 | 2-4 | |
十进制 | 16 | 8 | 4 | 2 | 1 | . | 1/2 | 1/4 | 1/8 | 1/16 |
转换示例
十进制 | 二进制 | 计算方式 |
---|---|---|
2.5 | 10.1 | 2.5=21+2-1 |
0.6 | 0.10011001… | 0.6=2-1+2-4+2-5+2-8+… |
上面我们发现0.6本身不是无限循环的小数,但是表示成二进制之后便成了无限循环小数。计算机是无法精确存储无限循环二进制数的,只能0舍1入。
二进制科学技术法
二进制科学技术法,就是将二进制的小数点左移或右移,直到首位为1,然后乘以2的多少次方,往左移几位就是2的几次方,往右移几位就是2的负几次方。小数点后面的0或1就叫做【尾数】,多少次方就叫做【指数】。
十进制 | 二进制 | 二进制科学技术法 |
---|---|---|
2.5 | 10.1 | 10.1=1.01 x 21 |
0.6 | 0.10011001… | 0.10011001…=1.0011001… x 2-1 |
要在计算机中存储一个浮点数,还需要有正负号。我们来看看浮点数是如何在计算机中存储的。在Java中,浮点数有float和double,其中float占4个字节【共32位】,double占8个字节【共64位】。
对于float,最高位1bit存储正负号,然后8位存储指数,其余全都用来存储尾数。对于指数部分,实际存储的是【指数+偏移量】之后的二进制结果,因为指数有负数,在加上偏移量之后就不会出现负数了。
案例
明白了上面浮点数相关的知识,我们来分析经典的【1-0.9】为什么不等于0.1。
首先来学一下二进制的减法是怎么运算的,如下:
搞明白二进制减法的运算方法,我们通过代码来实战一下:
- 通过计算出float类型1-0.9的二进制结果,然后转换成10进制输出结果
- 二进制转换成10进制的步骤在下图计算器中
/**
* @auther doaredo
* @mail doaredo@163.com
* @date 2021/07/31/1:22
* @description
*/
public class FloatDemo {
public static void main(String[] args) {
/*float经典:1-0.9*/
/*
二进制减法:1-0.9
1.000000000000000000000000
-0.111001100110011001100110
=0.000110011001100110011010*/
float a = 1f;
float b = 0.9f;
// 直接Java计算的结果
System.out.println(a - b);
// 自己二进制减法后的结果
System.out.println(0.0625 +
0.03125 +
0.00390625 +
0.001953125 +
0.000244140625 +
0.0001220703125 +
0.0000152587890625 +
0.00000762939453125 +
9.5367431640625e-7 +
4.76837158203125e-7 +
1.1920928955078125e-7);
// 我们发现自己计算的结果倍数比Java计算的要多
// java中自动对浮点数进行了四舍五入
System.out.println(0.10000002384185791f);
}
}
输出结果
0.100000024
0.10000002384185791
0.100000024
上面我们指定了数据类型为float,如果没有指定为float,那么默认为double类型。
- 通过计算出double类型1-0.9的二进制结果,然后转换成10进制输出结果
- 二进制转换成10进制的步骤在下图计算器中
/**
* @auther doaredo
* @mail doaredo@163.com
* @date 2021/07/31/1:22
* @description
*/
public class FloatDemo {
public static void main(String[] args) {
/*
* 经典:1-0.9
* 如果没有指定为float类型,默认为double类型的浮点数
* */
/*
二进制减法1-0.9
1.00000000000000000000000000000000000000000000000000000
-0.11100110011001100110011001100110011001100110011001101
=0.00011001100110011001100110011001100110011001100110011*/
// java计算结果
System.out.println(1 - 0.9);
// 自己二进制减法后的结果
System.out.println(0.0625
+ 0.03125
+ 0.00390625
+ 0.001953125
+ 0.000244140625
+ 0.0001220703125
+ 0.0000152587890625
+ 0.00000762939453125
+ 9.5367431640625e-7
+ 4.76837158203125e-7
+ 5.960464477539063e-8
+ 2.9802322387695312e-8
+ 3.725290298461914e-9
+ 1.862645149230957e-9
+ 2.3283064365386963e-10
+ 1.1641532182693481e-10
+ 1.4551915228366852e-11
+ 7.275957614183426e-12
+ 9.094947017729282e-13
+ 4.547473508864641e-13
+ 5.684341886080802e-14
+ 2.842170943040401e-14
+ 3.552713678800501e-15
+ 1.7763568394002505e-15
+ 2.220446049250313e-16
+ 1.1102230246251565e-16
);
}
}
输出结果
0.09999999999999998
0.09999999999999998
浮点数比较
由于浮点数占用的位数长度有限,当整数部分过大的时候,小数部分没就有充分的bit使用,导致精度问题。
/**
* @auther doaredo
* @mail doaredo@163.com
* @date 2021/07/31/1:22
* @description
*/
public class FloatDemo {
public static void main(String[] args) {
float c = 0.123456789f;
float d = 0.123456789f;
// 输出为true
System.out.println(c==d);
float a = 12345678.009f;
float b = 12345678.000f;
// 由于整数位占用bit过多,导致小数位没有地方而被忽略掉了
// 由于小数位被忽略了部分,实际比较两个数是相等的
System.out.println(a==b);
}
}
输出结果
true
true