浮点数在内存中的存储

时间:2023-03-12 14:13:42

  关于整型在内存中的存储,上一篇已经讲过主要内容了,这里再补充一些需要注意的点。

  整型在内存中存储的是补码,当我们在写无符号整型变量的时候,放在循环的继续条件判断时,如果没有考虑清楚无符号类型表示范围很容易出错或导致死循环。例如:

#include <stdio.h>

//这道题没有考虑无符号char的表示范围就很容易出错
int main()
{
unsigned char a[1000];
int i;
for(i=0; i<1000; i++)
{
a[i] = 1+i;//遍历数组
}
printf("%d", strlen(a));
return 0;
}

  解析:定义了一个有1000个元素,每个元素都是无符号char类型,通过循环给每个元素赋值。如果我们没有考虑到范围的话,你可能会答出随机值的答案,因为你觉得在整个数组里面是找不到'\0'或数值0的。这就是这道题考的地方,我们画个图就清晰了。

浮点数在内存中的存储

  从1开始,顺时针递增下去,当走到255时,再加1,由于256不在char表示范围内,它会被判断为0存入,看图即可理解。所以答案是255而非随机值。


  上面这道是范围导致的错误,下面这道是范围导致的死循环:

#include <stdio.h>

int main()
{
unsigned int i;
for(i=9; i>=0; i--)
{
printf("hello,world\n");
}
return 0;
}

  这里的坑就是,正常来讲,我们在当i逐渐递减达到0的时候,打印出9个hello,world后,i经过调整变为-1不符合继续执行的条件就跳出循环了。请注意!!这是unsigned int i。i = -1,整型常量-1的补码是32位全1,存入i中,最高位在无符号i看来就是有效位1,所以i是一个32位全1的超大数,肯定符合继续条件啦,也就是说,无符号i无论如何都是>=0的,判断条件恒成立,循环不停,死循环。

*在这里有个如何看待内存视角的先后顺序,-1是整型常量,补码是全1,放入i中,最高位不再是符号位,也就是最高位1不表示“负”的含义,变成正数了,整数原反补相同,全1补码就是全1原码。


OK,回到本篇主题,浮点数在内存中的存储0.0。

浮点数在内存中的存储

  n以整型的形式存放整型常量9,第一个打印没问题,第二个打印就不清楚来路了,通过指针解引用以浮点型给n存放浮点型常量9.0,第三个打印不是我们懂的,第四个是正常的。可以看出,以整型(浮点型)存放,用整型(浮点型)取出是符合预期的,而整型存放,浮点型取出或相反都是存在问题的,这是因为,整型和浮点型的存取不一样导致的。

  根据国际标准IEEE(电子和电气工程协会)754规定,任意一个浮点数可以用

(-1)^S * M *2^E表示,S的值是0或1,M是有效位,E是指数。

  eg:5.0的表示形式。

第一步:用二进制分别表示小数点前后的数--- 101.0

第二步:用科学计数法来表示--- 1.010*2^2 ,这里的1.010相当于M,2的阶乘2是E,我们可以类比十进制的科学计数比如100(一百)可以写成1.0*10^2。


S就是看5.0是真还是负来给就0或1就可以了,S是0就表示浮点数是正,相反1就是负。

  确定SEM后,我们还需要知道,在内存中是怎么给SEM分配空间的,S只表示0或1,只需要一个bit位就足够了。IEEE标准规定,float类型(总空间是4个字节),S占1个bit,E占8个bit,M占23个bit。double类型(总空间是8个字节),S一样,E占11个,M占52个。

浮点数在内存中的存储

  E的存法:IEEE规定E是一个无符号数,但我们知道,E可以是负数的,所以IEEE另外规定,E是加上一个中间值存到内存中,E是8个bit位(取值范围0-255)的中间值是127,E是11个bit位(0-2047)的中间值是1123。5.0用科学计数表示的E是2,当要存到内存时 2+127=129,其实存的是129。这样即使E是-1,也可以用126代替,取的是后减掉就可以了。

  M的存法:我们知道科学计数法的1<=M<2,M一定是表示成1.xxxx,为了让M的23个bit位都能用到,使精度更高,前面的1并不会存到内存中,而只存小数部分,后面有多余的位补零

  到这里,我们回过头来看看5.0的存法吧。 

(-1)^0 * 1.010 * 2^2    S   E   M

01000000101000000000000000000000 在内存中是以十六进制显示的

0100 0000 1010 0000 0000 0000 0000 0000-- 0x40a00000

浮点数在内存中的存储

由于编译器是小端存储,所以反着读就是40a00000。

现在明白浮点数的存储方式后,这道题输出的怪异结果就迎刃而解了。

浮点数在内存中的存储

以整型存放9,在内存中存补码,正数原反补相同;

补码-00000000000000000000000000001001

而以浮点数取出来 0 00000000 00000000000000000001001

S=0, E=0-127=-127, M=1.00000000000000000001001

那为什么是0.000000而不是1.000000呢,这里涉及到E为全0、全1、非全零全1的三种情况。

1、E为全零,那说明在存到内存前,E的值是-127,加上127存到内存里才是全零,那么这个数一定是超小的无限接近于零,所以ICCC对这种情况就规定,M的1不用补了而是还原为0.xxxxxx的小数,E的值是1-127=-126即为真实值;所以就变成+ 0.00000000000000000001001 * 2^-126;  读取小数点后六位就是0.000000。

2、E为全1, 那么就是在存之前E就是128的值,是一个正负接近无穷的数 (+-)1.xxxxxx*2^128; 

3、 E非全零全1是一般的正常情况,照算就可以了。

所以第二个printf打印出来的0.000000就解释完毕了。

以浮点数的形式存储9.0

(-1)^0* 1.0010  * 2 ^3-- 0 10000010 00100000000000000000000

以%d的形式打印01000001000100000000000000000000

浮点数在内存中的存储

以%lf的形式打印就是9.000000。

到这就是浮点数在内存中的存储啦!谢谢阅读。