例题引入
#include <stdio.h>
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;
}
这输出的是什么呢?
为什么这样呢?
这就涉及到了整数与浮点数在内存中存储方式的区别了。
一.整数在内存中的存储
我们之前在操作符学过:整数的二进制表示方式有三种,即原码、反码和补码。
三种表示方式都有符号位和数值位,符号位都是用0来代表“正”,1代表代表“负”。
以整型(int)为例;整型一共有32个比特位,其中第1位代表符号位,剩下的都是数值位。
1.1正整数与负整数的“原反补”区别
正整数的原码、反码和补码都是相同的。
负整数的三种表达方式各有不同:
原码:直接将属数值按照正负数的形式翻译成二进制
反码:不改动原码的符号位,其他位按位取反
补码:反码+1
小补充(补码转原码有两种方式):
第一种:补码-1 ——》 符号位不变,其他位按位取反
第二种:补码符号位不变,其他位按位取反 ——》再+1
对于整数来说:内存其实存放的整型的补码
为什么呢?
因为使用补码,可以将符号位和数值位进行统一处理
同时,加法和减法也能统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
2.浮点数在内存中的存储
在上面的代码中,num
和*pfloat
在内存中明明是同一个数,可为什么浮点数和整数的解读结果会差别的如此之大?
想要知道原因,一定要知道浮点数在内存的表示方式
2.1国际标准IEEE(电气电子工程师协会)754
根据国际标准IEEE 754,任意一个二进制浮点数V可以表示为下面的形式:
V = ( − 1 ) S ∗ M ∗ 2 E V = (-1)^S * M * 2^E V=(−1)S∗M∗2E
( − 1 ) S (-1)^S (−1)S表示符号位,当S = 0时,V为正数;当S = 1时,V为负数
M表示有效数字,M是大于等于1,小于2的(因为是二进制,如果大于2就代表还可以拆解)
2 E 2^E 2E表示指数位
举例
十进制的7.0写出二进制是111.0
,就相当与
1.11
∗
2
2
1.11*2^2
1.11∗22
按照上面的公式就可以知道,S = 0,M = 1.11,E = 2
-7.0的二进制为-1.11,
就相当与
−
1.11
∗
2
2
-1.11*2^2
−1.11∗22
S = 1, M = 1.11,E = 2
IEEE 754规定
对于32位的浮点数,最高的1位存储S(符号位),接着8位存储指数E,剩下的23位存储有效数字M。
对于64位的浮点数,最高的1位存储S(符号位),接着11位存储指数E,剩下的52位存储有效数字M。
2.2浮点数存储的过程
IEEE 745对有效数字M和E,还有一些特别规定
有效数字M
前面说过 ,1 <= M < 2
,也就是说,M可以写成1.xxxxx
的形式,其中xxxxx
表示小数部分。
IEEE 745规定,计算机在保存M时,这第一位总是1 ,因此这个1可以被省略,只保存后面的小数部分。例如保存1.01
时,只保存小数位01
,等到要读取的时候,再把1加上去,这样做的目的是节省1位有效字符位,可以更精确的储存数据。
指数E的情况就有些复杂
首先,E是一个无符号整型(unsigned int);
这样就意味着,如果E是不会出现负数的情况。但是我们知道,在科学技术发中的E是可以为负数的,所以IEEE 745规定,在存储E的真实值时必须要加中间数,对于8位的E来说,这个中间数为127;对于11的E来说,这个中间数为1023。
例如:
2 11 2^{11} 211的E是11,所以保存为32位浮点数时,必须保存成11+127 = 138,即
1000 1010
;
2 − 10 2^{-10} 2−10的E是-10,所以保存为32位浮点数时,必须保存为-10+127 = 117,即0111 0101
。
2.3浮点数读取的过程
指数E从内存的中取出来还可以分为三种情况:
1.E不为全0或不会全1
这时,浮点数的读取就会采取以下规则,即指数E的值减去127(或1023),得到真实值,再将有效数字M的前面加上第一位的1.
例如:
0.5存放的二进制为0.1,由于规定M必须大于等于1,即将小数点往右移1一位,则为1.0*2^{-1},其E就要为-1+127 = 126,表示01111110,尾数的1.0去掉1,为0,小数部位不足23位会补0,直到补齐23位,二进制表示为:
0 01111110 00000000000000000000000
E为全0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M也不会加上1,而是会还原成0.xxxxxxxx
的小数。这样做是为了表示
±
0
\pm0
±0,以及接近于0的很小的数字。
0 00000000 00000000000000000001001
E为全1
这时,如果有效数字M全为0,表示 ± \pm ±无穷大(正负取决与符号位)
0 11111111 00000000000000000000000
好了,关于浮点数的表示规则,就说到这里。
3.案例分析
我们回到一开始的例子;
第一段代码
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为:%d\n",n);//9
printf("*pFloat的值为:%f\n",*pFloat);//0.000000
先看第一段,为什么9还原成浮点数,就成了0.000000
?
9以整数的形式存储在内存中,得到如下二进制序列:
0000 0000 0000 0000 0000 0000 0000 1001
首先,我们将9的二进制序列按照浮点数的形式来拆解,
得到第一位符号位S = 0;
后8位的指数E = 0000 0000;
最后23位有效数字M = 000 0000 0000 0000 0000 1001
因为指数E全为0,所以符合第二种情况(E为全0),因此,浮点数就写成了:
V
=
(
−
1
)
0
∗
0.00000000000000000001001
∗
2
−
126
=
1.001
∗
2
−
146
V = (-1)^0 * 0.000 0000 0000 0000 0000 1001*2^{-126} = 1.001*2^{-146}
V=(−1)0∗0.00000000000000000001001∗2−126=1.001∗2−146
我们可以发现V是一个很小的且接近与0的正数,所以以十进制表示就是0.000000
。
第二段代码
*pFloat = 9.0;
printf("num的值为:%d\n",n);//1091567616
printf("*pFloat的值为:%f\n",*pFloat);//9.0
再看第二段,浮点数9.0,为什么打印为整数是1091567616
首先,浮点数9.0转化为二进制为1001.0,换成科学技术法为:
1.001
∗
2
3
1.001*2^3
1.001∗23
所以:
9.0
=
(
−
1
)
0
∗
(
1.001
)
∗
2
3
9.0 = (-1)^0 * (1.001)*2^3
9.0=(−1)0∗(1.001)∗23
那么,第一位的符号位S = 0;
指数E符合第一种情况(不为全0或不为全1);
E
=
3
+
127
=
130
E = 3+127 = 130
E=3+127=130二进制表示为10000010
;
有效数字M为001
后面加20个0,补满23位。
所以写成二进制,为S+E+M,即:
0 10000010 001 0000 0000 0000 0000
这个32位的二进制数,被当做整数来解析的时候,就是整数再内存中的补码,因为符号位为0,原反补都相同,所以原码翻译就是10911567616
。
结语
感谢您能阅读完此片文章,如果有任何建议或纠正欢迎在评论区留言。如果您认为这篇文章对您有所收获,点一个小小的赞就是我创作的巨大动力,谢谢!