浮点数在内存中的存储
观察下面的代码,输出结果是什么呢?
1 | int main() |
以整数存储,以整数取出,结果相同;以浮点数存储,以浮点数取出,结果也相同。
以整数存储,以浮点数取出,结果不同;以浮点数存储,以整数取出,结果也不同。
由此可见,整数和浮点数在内存中的存储是不同的。
下面我们看一下浮点数的存储规则,浮点数在内存中的表示是由IEEE(电气与电子工程协会)规定好的。
任何一个二进制浮点数,可以表示成
(-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
以上是关于浮点数存储的规则,下面是从内存中取出的规则。如何取出,分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;
好了,说了这么多,一言以蔽之:整数和浮点数在内存中的存储规则不同,导致输出结果不同。