浮点数是什么?
带有小数点的数就是浮点数,浮点数能表示小数和整数。浮点数家族有3种类型:float,double和long double类型。
例子:
- 浮点数3.14表示小数3.14
- 浮点数3.0表示整数3
为什么叫做浮点数?
在数学的科学计数法中,小数点在不同的位置也能表示同一个小数。
例子:
- 一个小数1234.5
- 用科学计数法可以等价于1.2345 x 103
- 也可以等价于12.345 x 102
观察这3个相同值中的小数点,像是在左右“浮动”,所以可以叫做浮点数。
浮点数存储规则
浮点数的规定标准是由IEEE(Institute of Electrical and Electronics Engineers)制定的IEEE 754标准。IEEE 754标准为浮点数提供了一套统一的表示方法和运算规则,使得不同计算机系统之间可以进行浮点数的交互和计算。
根据IEEE 754标准,任意的二进制浮点数由三个部分组成:符号位、指数位和尾数位。
- 符号位用于表示浮点数的正负;
- 指数位用于表示浮点数的数量级;
- 尾数位用于表示浮点数的精度。
用式子表示任意的二进制浮点数:(-1)S * M * 2E
- S是符号位,如果S等于0就是正数,S等于1就是负数;
- E是指数位,E是几,就是乘以2的几次方;
- M是尾数位,可以理解为小数点后面的数。
计算机只需存储符号位S,指数位E和尾数位M,就能存储一个浮点数。
已知一个十进制的浮点数,如何获取S,E和M呢?下面通过一个例子说明:
- 设一个十进制的浮点数为V并且令V = 5.5(假设这个浮点数是5.5)
- 把5.5转换为二进制的浮点数101.1。具体的换算过程如下图:
(需要注意二进制浮点数的权重,小数点右边从2的-1次方开始再到2的-2次方,以此类推)
- 把二进制的浮点数101.1转换成二进制的科学计数法的形式:1.011 x 22;
- 最后在1.011 x 22的前面再补上符号位即可,因为5.5是正数,所以乘上-1的0次方:(-1)0 x1.011x22
此时就能在(-1)0 x1.011x22 式子中得知S是0,E是2,M是1.011
再举一个浮点数为9.0的例子:
IEEE 754规定,存储浮点数时,根据精度的不同有两种存储方式:
- 32个比特位的单精度浮点数,其中最高的1位是符号位S(红色区域),紧接着的8个比特位是指数位E(绿色区域),在指数位后面的23个比特位是尾数位M(紫色区域)。
- 64个比特位的双精度浮点数(双精度浮点数比单精度浮点数更精确),其中最高的1位是符号位S(红色区域),紧接着的11个比特位是指数位E(绿色区域),在指数位后面的52个比特位是尾数位M(紫色区域)。
在上面提到过,内存想要存储一个浮点数,只需存储符号位S,指数位E和尾数位M即可。
根据IEEE 754的规定,之前的例子中算出来的指数位E和尾数位M还不能直接存储到内存中,E和M还有额外的要求:
- 尾数位M:规定M的取值范围是 1<=M<2,所以存储M时,整数部分的1直接省略,这样做的目的是可以少存储一个比特位,读取的时候再将整数部分的1补上即可,这就是为什么M叫做尾数位,因为存储M时只存储小数部分。
- 指数位E:规定存储E时,首先E得是一个无符号整型(unsigned int 类型)。
在存储32个比特位的单精度浮点数中,E(8bit)的取值范围是0 ~ 255;
在存储64个比特位的双精度浮点数中,E(11bit)的取值范围是0 ~ 2047。
但因为实际上科学计数法中的E可以是负数(以0.5为例)
为了避免E存储的是负数,IEEE 754规定存储E的真实值时,需要先加上一个中间数再存储到内存中。
在存储32个比特位的单精度浮点数中,这个中间数是127;
在存储64个比特位的双精度浮点数中,这个中间数是1023。
只有概念不好理解,举个例子,还是以0.5为例:
0.5转换成二进制的科学计数法中,E的真实值是-1,
如果0.5是以32位浮点数存储,那么E的真实值-1先加上127得到126,再把126存储到内存中;
如果0.5是以64位浮点数存储,那么E的真实值-1先加上1023得到1022,再把1022存储到内存中。
最后以5.5为例,完整的演示5.5怎么存储到内存中:
在内存中取出一个浮点数时,指数位E分为3种情况:
- E全为0,此时的浮点数一定是非常接近于0的数,直接表示0。
在32位上E的真实值是-126(1-127),在64位上E的真实值是-1022(1-1023),
M的整数部分不还原1,而是还原0,变成0.xxxx。 - E全为1,如果M全为0,表示正无穷大或负无穷大。
- 一般情况:把指数E的存储值减去127(或1023)得到E的真实值,
M的整数部分还原1,变成1.xxxx,符号位直接取,
最后用(-1)S * M * 2E得到结果。
一道例题
问以下代码分别输出的是什么:
#include<stdio.h>
int main()
{
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
*pFloat = 9.0;
printf("num的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
return 0;
}
output:
注释:
#include<stdio.h>
int main()
{
int n = 9;//整型的空间大小是32位
// 00000000 00000000 00000000 00001001
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n); //以整数的形式打印变量n中的数据9
//通过浮点型指针pFloat访问00000000 00000000 00000000 00001001的时候,
//是以浮点数的角度看待00000000 00000000 00000000 00001001的,
//所以00000000 00000000 00000000 00001001会被看做
// 0 00000000 00000000000000000001001
// S E M
// 0 -126 0.00000000000000000001001
// E在内存中是全0,所以E是-126
printf("*pFloat的值为:%f\n", *pFloat);//%f小数点右边只打印6位
*pFloat = 9.0;
//1001.0 - 二进制的9.0
//1.001 * 2^3 - 二进制的科学计数法
//(-1)^0 * 1.001 * 2^3
//S = 0 E = 3 M = 1.001
//0 10000010 00100000000000000000000
//9.0是以浮点数的形式放进变量n,但是变量n以整数的形式打印时,
// 会被认为01000001000100000000000000000000是补码,最高位是符号位,后面是数值位
//0 1000001000100000000000000000000
printf("num的值为:%d\n", n);//1,091,567,616
printf("*pFloat的值为:%f\n", *pFloat);//9.0
//注意打印的数据和打印的类型需要匹配
return 0;
}
完。