观察下面的代码,输出结果是什么呢?

1
2
3
4
5
6
7
8
9
10
11
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;
}

打印结果

以整数存储,以整数取出,结果相同;以浮点数存储,以浮点数取出,结果也相同。

以整数存储,以浮点数取出,结果不同;以浮点数存储,以整数取出,结果也不同。

由此可见,整数和浮点数在内存中的存储是不同的

下面我们看一下浮点数的存储规则,浮点数在内存中的表示是由IEEE(电气与电子工程协会)规定好的。

任何一个二进制浮点数,可以表示成

image-20220304192024795

(-1)^S表示符号位,s为0浮点数是正数,s为-1浮点数是负数;M表示有效数字,范围在[1,2);2^E表示指数位。

这样看很难理解,我们举例说明。

float a = -5.0f,浮点数-5.0,用二进制表示为-101.0,写成科学计数法的形式:-1.010*2^2,s=1,M=1.010,E=2

表示好之后,我们将它存入内存。IEEE 754规定,对于32位的单精度浮点数,最高位是符号位S,占1位,然后是指数位E,占8位,最后是有效数字M,占23位;对于双精度浮点数,占位分别为1,11,52,我们这里主要介绍float的存储。

![单精度浮点数在内存中的存储](

关于存储,E和M有一些特殊规定:

M范围是[1,2),所以保存时干脆省略1,比如-5.0的M是1.010,保存时直接写成010,后面再加20个0,凑够23位。即01000000000000000000.

E是无符号整数,但科学计数法E可能有负数,所以存入内存时,8位E加上127,11位E加上1023加以修正,比如-5.0的E是2,保存时加上127就是129,即10000001.

所以浮点数-5.0在内存中的存储是11000000101000000000000000000,用16进制表示就是0xc0a00000

-5.0的地址

以上是关于浮点数存储的规则,下面是从内存中取出的规则。如何取出,分3种情况,全看E。

①E不全为0或不全为1时,怎样存储的,就怎样取出。存储时M省略了1,取出是再加上1,存储时E加了127修正,取出时再减去127。

②E全为0,E=1-127=-126,M不再加上1,而是还原成0.xxxxx;

③E全为1,这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)

关于E取出的规则,简单了解下就好。

我们了解了IEEE标准之后,知道了浮点数在内存中的存储规则,下面来看一下上面那段代码。

第一个printf语句打印的是整数,整数存储,整数打印,结果当然是9;

第二个printf语句是以浮点数打印,结果是0.000000,以整数存储,以浮点数取出,结果必然不同。9以整数存储,二进制就是00000000000000000000000000001001,S=0,E=00000000,M=00000000000000000001001,以浮点数取出,因为E全为0,所以取出时E=1-127=-126,M忽略1,还原成0.00000000000000000001001,所以结果就是(-1)^0 * 0.00000000000000000001001 * 2^-126,也就是0.000000.

第三个printf语句是以整数打印,以浮点数存储,以整数取出,结果必然不同。9.0以浮点数存储,二进制就是1001.0,科学计数法表示为1.0010*2^3,其中S=1,E=3,M=1.0010,根据存储规则,存入内存中时,E=3+127=130,即10000010,M忽略1,结果为0010,凑够23位,即00100000000000000000000,占位比S:E:M=1:8:23,最终结果是01000001000100000000000000000000。以浮点数存储后,以整型打印,会将其视为整数,最高位是符号位0,正数原码反码补码一样,最终结果是1,091,567,616

第四个printf语句打印的是浮点数,以浮点数存储,以浮点数打印,结果是9.0;

好了,说了这么多,一言以蔽之:整数和浮点数在内存中的存储规则不同,导致输出结果不同。