我们都知道整形在内存中是按照补码的形式储存的,但是浮点数的储存却和整数的截然不同,浮点数没有所谓是原反补并且浮点数的大小很多情况下不能直接比较,这是怎么一回事呢?
引子
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(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式
(-1)^S * M * 2^E
(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。
举例来说十进制的20.0写成二进制是10100.0,那么换成浮点数一般储存方式是(-1)^0*1.0100 *2 ^4 。相当于S=0,M=1.0100,E=4。简而言之就是前面的-1用来判断正负,后面全部转化为科学计数法。
再举个例子十进制的-25.5写成二进制是11001.1(注意小数点后第一位的权重是2的-1次方,也就是0.5)。那么转换为一般浮点数存储方式就是(-1)* 1 *1.1001 *2^4。也就是S=1,M=1.1001,E=4。
具体的放入方式
S和 M的储存
S是用来判断正负的,是正就存0是负就存1。M我们前面也提到过必须先转换成按2的科学计数法,也就是2>M>=1(其实也很好理解,毕竟二进制最大就是2嘛)。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只23位,将第一位的1舍去以后,等于可以保存24位有效数字。
以上面的20.0为例,就相当于M=1.0100,只是存入了01000。
E的储存
首先要明确的是E是无符号整数,这意味着,如果E为8位,它的取值范围为0~ 255;如果E为11位,它的取值范围为0~2047。
但在实际中我们知道E是可能出现负数的(例如0.5写成科学计数法是1.0 * 2^-1 ,那么此时E=-1)。所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即0001001。
一个例子
取出主要分为三种情况。
一般情况(E不为全0或全1)
此时就是如何存入就如何取出。
例子
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)
可以发现我之前写的数全是20.0,·25.5这种能够被2除尽的数。那么如果是3.14这种数怎么写呢?
这是不能精确的转换为二进制,3.14中3可以用11表示但0.14呢?我们只能近似的写成001…010110…无论我们写多少为都不能精确的表达0.14。但如果写的位数越多那么就越接近0.14这个值,这也是为什么double比float精度高的原因。
二进制序列本身是没有任何意义的,当我们用不同方式去看这个序列时,该序列才被赋予了特定的含义。所以在使用时我们需要以什么方式存就以什么方式拿,不然会出现乱序。
总结:浮点数存储就是先将其化为二进制序列,然后再换成科学计数法,之后再分别按S,E,M存入不够位数后面补0。