浮点型在内存中的存储
引例
- 我们先来看下面一段代码
#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("n的值为:%d\n", n); printf("*pFloat的值为:%f\n", *pFloat); return 0; }
- 可能大部分小伙伴都会认为,打印的结果分别是:
n的值为:9 pFloat的值为:9.000000 n的值为:9 *pFloat的值为:9.000000
- 但实际上,运行得到的结果为:
n的值为:9 pFloat的值为:0.000000 n的值为:1091567616 *pFloat的值为:9.000000
- 可以看到,操作
float* pFloat = (float*)&n
,我们将整型n以浮点数的形式存入float型指针pFloat中,然后发现printf("pFloat的值为:%f\n", *pFloat)
打印结果错误- 当我们执行
*pFloat = 9.0
这一操作,即向整型n中存入float型的值,发现printf("n的值为:%d\n", n)
打印结果错误。
- 由此我们可以知道,在内存中,浮点数和整数的存储形式是不一样的,那具体有哪些差别呢?
浮点数的表示形式
- 根据国际标准IEEE(电器和电子工程协会)754,任意一个二进制浮点数v都可以表示为这个形式:
(-1)^S * M * 2 ^ E
- (-1) ^ S表示符号位,当S为0时表示正数,当S为1时表示负数。
- M表示有效数字,大于等于1,小于2
- 2 ^ E表示指数位
- 举个例子:
- 例如十进制浮点数5.5
- 其二进制形式为:101.1(如果不会怎么转换,请看二进制、八进制、十六进制与十进制的相互关系)
- 再用科学计数法表示:1.011 * 22
- 加上符号位:(-1)0 * 1.011 * 22
- 即这里的S = 0,M = 1.011,E = 2
- 因此,对浮点数进行存储时,只需要对S,M,E这三个数进行存储就行了。
浮点数的存储
- IEEE 754规定:
- 对于32位的浮点数(float型),最高的1位是符号位S,接着的8位是指数E,剩下的23位是有效数字M
- 对于64位的浮点数(double型),最高的1位是符号位S,接着的11位是指数E,剩下的52位是有效数字M
- IEEE 754对有效数字M和指数E还有些特殊的规定:
- 对于有效数字M:
- 之前说过,
1<=M<2
因此M总是可以写成1.XXXXX
这样的形式- 754规定,在计算机内部保存有效数字M时,默认这个数的第一位总是1,因此可以被舍去,只保留后面的
xxxxxx
部分。- 等到读取的时候,再把第一位的1加上去。
- 这样做,就可以节省一位有效数字,以double型为例,原本M为52,只能存储52为有效数字,但舍去第一位后,就相当于可以存储53位数字了。
- 对于指数E:
- 首先我们需要知道,指数E为一个无符号整数(unsigned int)
- 以float型为例,E所能表示的范围是0~255。但问题是,在实际的表示中,指数E也可能存在负数的情况,例如十进制浮点数0.5,转换为二进制为0.1,则S = 1,E = -1,M = 1,那么怎么处理E为负数的情况呢?
- IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127,对于11位的E,这个中间数是1023。例如对于十进制浮点数0.5,存入内存时,E的值应该是-1 + 127 = 126
- 将指数E从内存取出要分以下三种情况:
E不全为零且E不全为1
- 这时,指数E的真实值就是存储值减去中间值(127/1023)
- 有效数字M需要加上第一位的1。
E全为0
- 这时,指数E的真实值就是(1 - 127) / (1 - 1023)(标准规定)
- 有效数字M不需要加上第一位的1,而是还原为
0.xxxxxxx
的小数。这样做是为了表示±0,以及几近于0的极小的数字(标准规定)E全为1
- 这时,有效数字M全为0,表示±∞(正负取决于符号位)(标准规定)
Eg
对于十进制浮点数5.5
十进制float型 -> 5.5 二进制 -> 101.1 二进制科学技术 -> 1.011 * 2 ^ 2 加上符号位 -> (-1) ^ 0 * 1.011 * 2 ^ 2 S = 0, M = 1.011, E = 2 存入到二进制序列:0 10000001 01100000000000000000000 转换为十六进制:4 0 B 0 0 0 0 0
总结
- 我们再来回顾上面引例的代码:
#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("n的值为:%d\n", n); printf("*pFloat的值为:%f\n", *pFloat); return 0; }
- 整数9在内存中的存储形式为
0000 0000 0000 0000 0000 0000 0000 1001
- 我们用float类型的指针pFloat指向这段整型内存,那么根据上面的知识,我们可以得到:
- 指数E全为0,因此
printf("pFloat的值为:%f\n", *pFloat);
打印出来的结果就是pFloat的值为:0.000000
- 之后,将这段内存改为浮点数9.0
- 9.0的二进制序列为:
1001.0
- 写成科学计数法为:
(-1) ^ 0 * 1.001 * 2 * 3
,即S = 0, E = 3, M = 1.001
(存入内存时,E = 127 + 3 = 130,M = 0.001),即:0 10000010 00100000000000000000000
- 如果我们用读取整数的方法来读取这段内存,那结果就是
1,091,567,616
也就是printf("n的值为:%d\n", n)
的打印结果
用指针改变内存和强制转换的区别
- 通过对浮点数在内存中如何储存的了解,我们应该知道,用指针改变内存的读取和强制转换变量类型是两个截然不同的操作
- 强制转换类型:
float num_1 = 3.14; int num_2 = (int)num_1; printf("%d\n",num_2); /* 将float强制转换为int只是简单的对小数点之后的数据进行舍去,而没有改变对内存的处理 打印结果为3. */
- 指针改变内存
float num_1 = 9.5; int *num_2 = (int *)&num_1; printf("%d\n",*num_2); /* 用读取整型数据的方式读取float型的内存 打印结果为1092091904 */