float的范围和精度(为什么float的容量比long高?)
在[Java基本数据类型详解(为什么byte的范围是-2^7 ~ 2^7-1?)]((7条消息) Java基本数据类型详解(为什么byte的范围是-2^7 ~ 2^7-1?)_武正康的博客-CSDN博客)中我们聊到,float占据四个字节,而long占据八个字节,可是当float类型的数据转换成为long类型时仍然需要强制类型转换,这是为什么呢?今天我们就聊一聊float范围和精度。
float的存储方式
我们知道科学计数法是(±)a.b*10c,其中a的范围是1到9,b是小数点之后的所有数字,c是10的指数。而计算机中存储的是二进制数字,所以float的存储的数字要转换成(±)a.b*2c,由于二进制中最大的数字就是1,所以可以写成(±)1.b*2^c,因此计算机只需要储存(±),b和c就可以了。
float占用四个字节32位划分为:
- Sign(1位):用来表示浮点数是正数还是负数,0表示正数,1表示负数。
- Exponent(8位):指数部分。即上文提到数字c,但是这里不是直接存储c,为了同时表示正负指数以及他们的大小顺序,这里实际存储的是c+127。
- Mantissa(23位):尾数部分。也就是上文中提到的数字b。
float的存储示例
以数字8.125为例,看一下这个数字是怎么存储在float变量中的:
- 整数部分转换为二进制是1000。
- 小数部分转换为二进制是0.001。
- 拼接在一起是1000.001,类似于科学计数法的样式得到1.000001*2^3。
- 从上面的结果中我们可以得到符号为正,b为000001,c为3,这里指数部分实际存储的是130。
具体的表示为:
S | E | E | E | E | E | E | E | E | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M | M |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
note(小数部分如何转换为二进制)
小数部分乘以2,得到的结果取整数部分依次从左往右放到二进制数小数点后,直至结果的小数点后为0。例如十进制的0.125,要转换为二进制的小数
步骤数 | 结果 | 整数部分 |
---|---|---|
1 | 0.125*2=0.25 | 0 |
2 | 0.25*2=0.5 | 0 |
3 | 0.5*2=1 | 1 |
所以十进制的0.125转换为二进制位0.001。
float的范围
明白了了float的存储原理之后便能计算float的范围,找到所能表示的最大的数,变换以下符号之后便是所能表示的最小的数。
那么可以得到尾数0.1111111 11111111 11111111,指数为1111 1111,但是指数全是1时有特殊用途,所以指最大时1111 1110,指数减去127得到127,所以最大的数字就是1.1111111 11111111 11111111 * 2^127,这个值为340282346638528859811704183484516925440,通常表示为3.4028235E38,那么float的范围就出来了:[-3.4028235E38, 3.4028235E38]。
float的精度
先看整数部分
指数位有8位,范围是[0,255],指数为全部为1有其他的用途,所以范围是[0,254],根据指数的存储规则,实际上指数的范围是[-127,127]。
尾数位一共有23位,范围是[0,223-1],而公式的表示发为1.b*2c,因此尾数位前面还有一个1,因此实际范围是[0,224-1],换算成十进制就是16777215,所以整数[0,16777215]都能精确的表示,因为他们都能写成1.b*2^c的形式,只要配合调整指数c就可以了。
那不超过最大范围的整数能否精确表示呢?
更大的数字如16777216,因为正好是2 的整数次幂,所以这个数字是可以精确表示的。
再大一点呢?
实际上只要在float的范围内,能够写成1.0000000 00000000 00000000*224*2n + 0.xxxxxxx xxxxxxxx xxxxxxxx*224*2^n的整数都能够精确表示。
解释下这个公式,对于16777216之前的整数都是能够连续精确表示的,但是在其之后便开始出现跳跃,随着n的逐渐增加,跳跃间隔也逐渐变大。
例如当n=1时,每次跳跃间隔为225,当n=2时,每次跳跃间隔为2^26。
再看小数部分
并不是所有的小数都能够精确的表示,小数部分的表示和整数部分密切相关。
例如1.0000000 00000000 00000000*27,这里23位尾数位中有7位是用来表示整数部分的,剩下的16位才是用来表示小数部分的。
假设有m位是用来表示小数部分的,那么小数部分的二进制表示为0.xxxxxxxx(一共有m个x),在这个前提下能够用哦0.xxxxxxx(一共有m个x)所能表示的小数便是精确的小数。
顺便说明以下,当指数为超过23时,便无法在表示小数部分。
float的特殊值
符号位 | 指数位 | 尾数位 | 数值 | 含义 |
---|---|---|---|---|
0 | 全为0 | 全为0 | +0 | 正数0 |
1 | 全为0 | 全为0 | -0 | 负数0 |
0 | 全为0 | 任意取值f | 0.f*2126 | 非标准值,尾数前改为0,提高了精度 |
1 | 全为0 | 任意取值f | -0.f*2126 | 非标准值,尾数前改为0,提高了精度 |
0 | 全为1 | 全为0 | +infinity | 正无穷大 |
1 | 全为1 | 全为0 | -infinity | 负无穷达 |
0/1 | 全为1 | 不全为0 | NaN | 非数字,用来表示一些特殊情况 |