引言
在前面的内容中我们提到过整形数据在内存中是以补码的形式存储的。包括char、short、int、long、long long。字符整形(char)存储的是其ASCII码值,储存形式与整形一致,只是取出的时候解读形式不同(%c)。
但是我们似乎无法找到浮点型数据的原码、反码、补码,那么浮点数在内存中到底是如何存储的呢?
浮点数的储存
首先,毋庸置疑的是,数据在内存中一定是以二进制的形式存储的。所以,要解决浮点数在内存中的存储就是要解决两个问题:如何用二进制表示浮点数的数值;如何表示小数点的位置。
规则
数值的转换
这是一个数学问题:对于一个二进制数,小数点前的第一位的权重是2^0、第二位是2^1、第三位是2^2依此类推;在小数点的后面第一位的权重是2^(-1)、第二位的权重是2^(-2)依此类推。
所以,浮点数5.5用二进制小数表示就是101.1。
如何表示小数点
根据国际标准IEEE(电气电子工程师学会)754,任意一个二进制浮点数都可以表示为
(-1)^S*M*2^E
其中 (-1)^S表示符号位;M表示有效数字(1<=M<2);2^E表示指数位。这样,就可以通二进制科学计数法的方式来实现小数点位置的表示。
例如:对于5.5,S=0表示是一个正数;M=1.011表示其有效数字;E=10表示小数点向左偏移了两位。
存放格式
当我们要把这三个数据存放到内存中时IEEE规定了其格式:
对于单精度浮点数(float):其大小是4个字节也就是32个比特位。其中第一位存放S;后面的8位存放E;剩下的23位存放S。
对于双精度浮点数(double):其大小是8个字节也就是64个比特位。其中第一位存放S;后面的11位存放E;剩下的52位存放S。
要求每一部分存储的数据都是无符号整数且每一部分都有固定的存放内容,当这部分的值存入后,剩余的位补0。
但是这样的存放是存在一定的问题的:1、虽然用科学计数法制式确定了小数点的位置,但是还是不能将M直接存放在内存中。2、当要存储0.5这样的浮点数时,E的值将是一个负数,也是不能直接存储的。
为了解决这样的问题IEEE规定:1、W在存入内存时需要减1,也就是只存储W的小数部分。2、在存入E时会给其加上当它为负数时非符号位的最大值得到它的计算值,对于float就是加上7位的最大值127;对于double就是加上10位的最大值1023。
所以5.5在内存中存储的真正形式就是S=0;M=1.011-1;E=10+1111111。即0 10000001 01100000000000000000000。
特别规定
当取出这个浮点数时,IEEE有一些特别规定。
E不全为0且不全为1
在取出时给E的计算值减去127或1023即可得到真实值。此时M的计算值加1化为真实值然后进行计算。
E全为0
E的计算值为0即的真实值为-127或-1023,表示这个浮点数无限接近于0。此时M不再加一,直接还原为0.xxx的小数,同时E还原为1-127或1-1023。然后进行计算
E全为1
E的计算值全为一即E的真实值为255-127或2047-1023,表示这个浮点数为正/负无穷大。
举例
在了解了浮点数的存储后,我们来分析一段代码:
#include<stdio.h>
int main()
{
int n = 9;
float *p = (float*)&n;
printf("%d\n", n);
printf("%f\n", *p);
*p = 9.0;
printf("%d\n", n);
printf("%f\n", *p);
return 0;
}
这段代码打印的结果不同是存入的数据类型和取出的数据类型共同决定的,接下来我们逐句分析:
首先创建了一个int型的变量n,赋值为9。又创建了一个float*的指针p,将&n强转为float*型并赋给p。
这时,p所指空间(与&n所指向空间相同)内存储的内容就是9的二进制补码(9是正数,原反补相同)为00000000000000000000000000010001。
这时我们通过%d(十进制整型)打印n中的内容时结果就是9。但是当用%f(单精度浮点型)打印*p中的内容时,编译器会将这段二进制数解释为一个单精度浮点数,这个浮点数的S=0、E=00000000、M=00000000000000000001001。此时E为全0,这个浮点数E的真实值即为-126;M的真实值即为0.0000000000000000001001。这个数无限接近于0,打印出来即为0.000000。
接下来,将*p改为了一个浮点数9.0,所以p所指向空间(与&n所指向空间相同)中存储的内容是浮点数的9.0的二进制数。对于9.0、真实值为S=0、M=1.001、E=3;转化为计算值就是S=0、M=001、E=10000010。所以9.0的二进制表示为0 10000010 00100000000000000000000。
这时我们通过%d打印n时即把&n指向的二进制数01000001000100000000000000000000当作整型来打印,结果就是1091567616这个很大的数;但是通过%f打印*p时(p的类型是float*则*p的类型就是float)就是把这个二进制数当作浮点数打印,结果自然是9.000000。
需要注意的是:数据的类型与取出时的类型要尽量一致,否则会导致一些难以预料的结果。当然如果想要实现某些特殊的目的除外)。
总结
在这篇博客中我们简单的了解了浮点数在内存中是如何存储的。
如果大家发现了什么问题欢迎指正。
希望与大家共同进步哦