1.浮点数是什么
在计算机中表达实数的方式有定点数和浮点数。定点数的小数点位置固定,不能移动,浮点数的小数点位置可以移动。
所以一个浮点数可以有多种表达方式,如101.101可以表示成1.01101*2^2、1011.01*2^-1。
Java的float和double采用了IEEE 754标准中所定义的单精度 32 位浮点数和双精度 64 位浮点数的格式,用二进制的科学计数法来表示浮点数。
2.浮点数的表示
Java语言的浮点数有两种表示形式
十进制表示:123.0,23.45
科学计数法表示:8.3e2,9.2E-3
1> e是exponent的首字母,指数的意思,表示10的几次方,大小写均可
2> e或E之前必须有数,注意指数有正有负
3> 只有浮点类型的数值才可以使用科学计数法形式表示。例如,51200是一个int类型的值,但512E2则是浮点类型的值
3.浮点数的内存
float在内存中占4个字节32位,内存区域分为三个部分。
尾数位:0-22位,共23位
指数位(阶码位):23-30位,共8位
符号位:最高位,0表示正数1表示负数
double在内存中占8个字节64位。最高位为符号位,接下来11位为指数位,最后52位为尾数位
根据IEEE754标准规定
对于阶码e ,如果不是0或者255, 就需要减去偏差值,对于float 是127 ,double是1023。减去偏差值的数值才是真正的指数
对于尾数M ,如果阶码不是0或者255,尾数的小数点左侧有一个默认的 1
4.浮点数的进制转换
十进制小数转二进制小数,整数部分和小数部分需要分开处理
整数部分:除以2直到商为0,反序取余
小数部分:乘以2,取结果的整数部分,再用结果的小数部分乘以2,如此循环下去,直到小数部分为0。然后将整数顺序排列。
如果小数部分永远不为0,则按规定进行取舍。
(因为可能出现永远不为0的情况,所以就注定了有些十进制小数无法用二进制小数精确表示)
来看一个具体例子
float f= 10.8125f;
10.8125整数部分转换为二进制是1010,小数部分转换成二进制是1101
所以10.8125对应的二进制小数位1010.1101,规范写法为1.0101101*10^3
底数去掉1和小数点为0101101,指数为3+127=130,二进制为10000010,符号位为0
即10.8125的二进制为
0100 0001 0010 1101 0000 0000 0000 0000
用程序看一下10.8125的二进制表示
System.out.println(Integer.toBinaryString(Float.floatToIntBits(10.8125f)));
结果为
1000001001011010000000000000000
前面补0为
0100 0001 0010 1101 0000 0000 0000 0000
跟我们计算的一样
5.浮点数不精确
浮点数在计算机中用以近似表示任意某个实数。近似,是什么意思呢?就是用一个近似值去表示一个数。
浮点数并不一定等于小数。
如float f = 0.1f ;计算机底层是无法用二进制去精确表示0.1的,它只会有一个无限接近于0.1的二进制小数
0.1f 在计算机中存储的二进制是 0011 1101 1100 1100 1100 1100 1100 1101,相当于10进制的0.100000001490116119384765
在计算机中,用0.100000001490116119384765这个近似值去表示小数0.1
打印出来也是0.1
那为什么0.1无法精确保存打印出来的却还是0.1?
因为浮点数就是用这个近似值去表示0.1的
只要你的二进制存储是这个,那么,Java会认为你就是浮点数0.1
尽管按照十进制的算法,这个数字并不等于0.1而是趋近于0.1.
但这也恰好印证了,浮点数不等于小数,而是以近似的方式去表示实数的
还有一种情况
float f = 5.2345556f; System.out.println(f);//5.2345557
把5.2345556f赋值给一个float类型变量,接着输出这个变量时看到这个变量的值已经发生了改变。
为什么打印出来数字会发生变化?
1.计算机无法用二进制小数精确表示一个十进制小数
float f = 5.2345556f;
在内存中的二进制为0100 0000 1010 0111 1000 0001 0111 1011,转换为十进制为5.234555721282958984375,保留8位精度为5.2345557
2.浮点数的取值范围
浮点数是有取值范围的,float的取值范围是-3.4*10^38 ~ 3.4*10^38
那么,它能够表达数轴上在这个范围内的所有数吗?显然是不能的,首先无限小数就无法表示。
如果把浮点数能够表示的所有数在数轴上一一列出来,我们会发现这不是一个完整的线段,而是中间带有间隔
参考:https://my.oschina.net/jasonli0102/blog/3013198
6.当我们定义float f = 0.1时, 计算机是如何进行保存的?
首先分配4个字节去存储,然后把符号位、尾数位、指数位分别填入
这里我们看一下尾数位
将十进制的0.1 转换为二进制
小数部分乘以2 0.1*2 = 0.2; 0.2*2=0.4; 0.4*2=0.8; 0.8*2=1.6; 0.6*2=1.2;(这里小数部分又得到了最开始的2,这意味着会无限循环下去)
取整数部分 0 0 0 1 1 (接下来取到的整数是 0 0 1 1的循环)
所以0.1对应的二进制小数为0.0 0011 0011……(0011循环)
将其转换为浮点数的规范表达形式, 1.1001100110011……*2^-4
float的尾数除去默认的小数点前面的1,有23位。所以将小数点后面的值依次填入尾数,最后一位按照规定进行取舍,剩下的直接舍弃
忽略最后一位的取舍的话,尾数应该是 100 1100 1100 1100 1100 1100
查看一下0.1的二进制表示
System.out.println(Integer.toBinaryString(Float.floatToIntBits(0.1f)));//111101110011001100110011001101
即尾数应该是 100 1100 1100 1100 1100 1101,除了最后一位,其他跟我们预想的一样
7.阶码为何使用偏差值,float偏差值为何是127?
float的偏差值是127(2^7-1),double的偏差值是1023(2^10-1)
阶码使用偏移量是为了简化运算。
指数是有正有负的。如果把指数位的最高位作为符号位的话,一个浮点数中就会有两个符号位了,那浮点数之间的比较和运算想必会困难许多。
使用了偏移量之后,用无符号整数既可以表示正指数,又可以表示负指数
为什么偏移量是127?
8位可以表示 0000 0000 ~ 1111 1111 即0~255共256个值
规范规定0000 0000与1111 1111用作特殊情况,所以除去0和255,阶码能表示1~254共254个值
为了平衡正负指数,选取了1~254中间的值127。
8.浮点数取值范围
-2*2^127 ~ -1*2^(-126) 与 1*2^(-126) ~ 2*2^127
转化得:
-3.4*10^38 ~ -1.2*10^(-38) 与 1.2*10^(-38) ~ 3.4*10^38
9.浮点数的大小比较
在二进制中,是通过符号位、指数位、尾数位分别比较得到结果的
有一个不相等则不相等
注意指数位的比较,比较的是减去偏移量之后的数
10.浮点运算
浮点计算是指浮点数参与的运算,这种运算通常伴随着因为无法精确表示而进行的近似或舍入
对于0.1+0.2,为什么不精确?
因为这种情况,至少有一个就不能精确表示,然后运算的时候是底层二进制进行运算。