先看下面一段程序:
- #include <stdio.h>
- int main(void)
- {
- int a = 0x42cc0000;
- float b = 102.0;
- printf("%.2f\t%.2f\n", (float)a, *(float*)&a);
- printf("%d\t%#x\n", (int)b, *(int*)&b);
- return 0;
- }
运行结果如下:
1120665600.00 102.00
102 0x42cc0000
其中 1120665600 即为 0x42cc0000 的十进制形式。
下面将分析结果为什么会这样。
首先,用下面的宏判断系统是大端小端:
- #define little_endian() ((*(int *)"123" & 0xff) == '1')
由于我们主要是在x86 32位系统上,故int和float都占4字节。
且系统为小端,而所谓小端,简单地说,就是高位在高,低位在低。
我们对整型数已经很熟悉了,只是对浮点数还比较陌生,可能平时接触得不多的缘故吧,
接下来我们将好好了解浮点型数的内存布局。看如下代码:
- static inline int bit(char *s, int i)
- {
- return (*(s + (i >> 3)) & 1 << (7 - (i & 7))) != 0;
- }
- static void show_bit(char *s, int from, int to)
- {
- int i;
- for (i = from; i <= to; i++)
- printf("%d", bit(s, i));
- printf(" ");
- }
关于以上 bit 函数还有一个小插曲,在 7 - (i & 7) 这个表达式中当没有这个括号时由于减号优先级高于 & ,逻辑上是不对的,然而我发现这不影响结果,大家可思考一下,为什么在这里即便没有这个括号,逻辑上错误,但是结果仍然是对的呢?
给定浮点数 b = 102.00, 我们调用函数 show_bit((char *)&b, 0, 31); 即可得到该浮点数在
内存中的位级表示,即为 0000 0000 0000 0000 1100 1100 0100 0010表示为十六进制即为 0x42cc0000。
由于是小端系统,所以我们将字节序列倒过来,如下
0100 0010 1100 1100 0000 0000 0000 0000
我们可将其分为三段,
第一段为第一个bit,表示符号,决定正负,这里为0,故为正数。
第二段为接下来的8bit,表示指数部分,在这里为1000 0101,十进制为133
浮点标准中规定,在这8bit非全0和非全1的情况下,指数的值为E = e - Bias
e即为这8bit代表的无符号数,Bias对于单精度浮点为127,因此在这里指数的值为
E = e - Bias = 133 - 127 = 6 补充一下,当这8bit全0时,指数值为E = 1 - Bias而非-Bias
第三段为剩余的23bit,表示小数域部分f,在这里是 1001 1000 0000 0000 000
即为0.10011,化成分数形式为19/32.
如果指数部分非全0和非全1,那么有效数M = 1 + f,否则M = f。
因此在这里有效数M = 1 + 19/32
综上,我们将该字节序列重新解读回float,即为
(1 + 19/32) * 64 = 102,其中64为2的6次方。
到这里,不知大家是否了解了呢?
写了如下代码,相信有助于大家了解浮点数的内存布局:
- void show_bit32(int i)
- {
- char *s = (char *)&i;
- show_bit(s, 0, 0);
- show_bit(s, 1, 8);
- show_bit(s, 9, 31);
- printf("\n");
- }
- void show_float(float s)
- {
- char *ptr = (char*)&s;
- char buf[4] = {ptr[3], ptr[2], ptr[1], ptr[0]};
- show_bit32(*(int*)buf);
- }
- char *input(void)
- {
- static char buf[128];
- printf(">>> ");
- memset(buf, 0, sizeof buf);
- fgets(buf, sizeof buf, stdin);
- if (buf[strlen(buf) - 1] == '\n')
- buf[strlen(buf) - 1] = '\0';
- if (!*buf || strcmp(buf, "exit") == 0)
- return NULL;
- return buf;
- }
- int main(void)
- {
- float f;
- char *ptr;
- while ((ptr = input()) != NULL) {
- if (sscanf(ptr, "%f", &f) != 1)
- continue;
- show_float(f);
- }
- return 0;
- }
关于double型,原理大致相同,只是double为8字节,指数域为11位,小数域为52位而已,
如想深入了解相关机理,可研读《深入理解计算机系统》相关章节,这里就不赘述了。