1、整数型存储
整数型存储就是所有整型家族里的数据类型的存储方式,也就是说包含了字符类型的存储(因为字符的''
操作符的返回值是ASCII码值,故实际上存储的是整数)。
1.1、有符号整数
有符号整数包含char
,short
,int
,long
,long long
这几种类型的数据。
他们的二进制表示方法有三种:原码,反码,补码。
这三种表示方法均有符号位和数值位两部分,符号位都是用0
表示正,用1
表示负,最高位的一位是被当做符号位,剩余的都是数值位。
原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
正整数的原、反、补码都相同;负整数的三种表示方法各不相同。
有符号整数在内存中统一以补码的形式存储,并且以补码形式参与任何操作。
以补码形式进行存储的原因:
在计算机系统中,有符号数值一律用补码来表示和存储。
原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
1.2、有符号整数的特性
由于补码与数据类型限制精度的特性,产生了一种有意思的循环:
以char
类型为例子:
-128,-127…-2,-1,0,1,2…126,127,-128,-127…-2,-1,0,1,2…126,127,-128,-127…
总结为:阳极生阴,阴极生阳(有点模运算的感觉在里面)。
1.3、无符号整数
无符号整数包含unsigned char
,unsigned short
,unsigned int
,unsigned long
,unsigned long long
这几种类型的数据。
无符号数中没有原码-反码-补码
的概念。
无符号整数的二进制表示形式只有一种,即直接将无符号整数翻译成对应的二进制形式,并且以这种形式在内存中存储。
1.4、无符号整数的特性
由于数据类型限制精度的特性,也产生了一种有意思的循环:
以char
类型为例子:
0,1,2…126,127,128…254,255,0,1,2…126,127,128…254,255,0,1,2…126,127,128
2、浮点数型存储
使用浮点数型存储的数据类型有:float
、double
、long double
。
浮点型数存储也就是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 M:表示有效数字,M取值范围是大于等于1,小于2。
2 E 2^E 2E:表示指数位。
IEEE 754
标准的表示规则可总结为:
1、十进制浮点数转换为二进制浮点数。
2、用科学计数法表示此二进制浮点数。
3、表示为IEEE 754
格式。
IEEE 754
标准的存储规则是:
将IEEE 754
格式中的 S S S、 M M M、 E E E分别提取出来,并且对 M M M、 E E E进行处理后(对 S S S不做处理),得到:符号码S
、尾数码M
、阶码E
。
对 M M M的处理:
前面说过,1≤ M M M<2,也就是说, M M M可以写成1.XXXXXX
的形式,其中XXXXXX
表示小数部分。IEEE 754
规定,在计算机内部保存 M M M时,默认这个数的第一位总是1
,因此 M M M整数位的1
需要被舍去,只保存后面的XXXXXX
部分,得到尾数码M
。
比如 M M M为1.01
的时候,只保存01
,等到读取的时候,再把整数位的1
补回去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给尾数码M
只有23位,将整数位的1
舍去以后,等于可以保存24位有效数字。
对 E E E的处理:
首先,将 E E E转换为二进制数据。
IEEE 754
标准规定,处理后得到的阶码E
是一个无符号整数(unsigned int
)。这意味着,如果阶码E
为8
位,它的取值范围为0~255
;如果阶码E
为11
位,它的取值范围为0~2047
。
但是,我们知道,科学计数法中的 E E E是可以出现负数的,所以IEEE 754
规定, E E E必须再加上一个偏移量
来变成一个非负数,也就是阶码E
。对于8
位的 E E E,这个偏移量是127
;对于11
位的 E E E,这个偏移量是1023
。
比如, 2 10 2^{10} 210的 E E E是10
,所以在保存为32位浮点数时,必须对 E E E进行处理得到阶码E
:10+127=137
,即10001001
。
注意:以float
类型为例,值为-127
和128
的这2个 E E E有特殊用途,一般不在正常的 E E E讨论范围(-126~127
)内,他们的阶码E
分别是:
-127
:-01111111
+ 01111111
= 00000000
。
128
:10000000
+ 01111111
= 11111111
。
在得到最终的符号码S
、尾数码M
、阶码E
后,按照以下规则将它们连接在一起,得到最终的浮点数型存储的二进制数据:
IEEE754
标准规定:
对于32位的浮点数(float
类型),最高的1位存储符号码S
,接着的8位存储阶码E
,剩下的23位存储尾数码M
。
对于64位的浮点数(double
类型),最高的1位存储符号码S
,接着的11位存储阶码E
,剩下的52位存储尾数码M
。
对应例子(十进制浮点数5.5
用float
类型变量进行存储):
1、转换为二进制浮点数: 101.1 101.1 101.1。
2、科学计数法表示为: 1.011 ∗ 2 2 1.011*2^2 1.011∗22。
3、IEEE 754
格式: V = ( − 1 ) 0 ∗ 1.011 ∗ 2 2 V=(-1)^0*1.011*2^2 V=(−1)0∗1.011∗22。
4、经过处理后得到:符号码S
为0
;尾数码M
为0110000 00000000 00000000
;阶码E
为10000001
。
5、连接得到最终用于存储的二进制数据:0 10000001 0110000 00000000 00000000
。
验证:
3、浮点数型读取过程
从内存中读取浮点数型的二进制数据时,根据阶码E
的不同,可以分为三种情况:
3.1、阶码E
不全为0也不全为1(通常情况)
此时就直接使用浮点数型存储的逆过程(以float
类型为例):
1、将存储的32位二进制数据切割为:1位的符号码S
,8位的阶码E
,23位的尾数码M
。
2、将阶码E
减去偏移量127
,还原为 E E E;将尾数码M
补上小数点.
和整数位的1
,还原为 M M M。(符号码S
没有还原过程,直接就是 S S S)。
3、根据IEEE 754
格式: V = ( − 1 ) S ∗ M ∗ 2 E V=(-1)^S*M*2^E V=(−1)S∗M∗2E得到科学计数法的二进制浮点数。
4、最后将二进制浮点数转换为十进制浮点数。
3.2、阶码E
全为0
以float
类型为例:
1、将存储的32位二进制数据切割为:1位的符号码S
,8位的阶码E
,23位的尾数码M
。
2、将阶码E
减去偏移量127
,还原为 E E E,可是由于 E E E为-127
过于小,能够预测到最终的结果是一个非常小的数字,此时的尾数码在补上小数点.
且在整数位补0而不是补1,并且让 E E E+1。这样做的目的是为了表示一个无限接近于±0的数字,也就是: ± 0. x x x x x x x ∗ 2 − 126 \pm0.xxxxxxx*2^{-126} ±0.xxxxxxx∗2−126。
3、所以直接规定:阶码E
全为0,表示浮点数无限接近于 ± 0 \pm0 ±0。(由于编译器输出浮点数时的精度限制,会输出为0.000000
)
3.3、阶码E
全为1
与以上同理:
阶码E
全为1,表示浮点数无限接近于 ± ∞ \pm\infty ±∞。
4、浮点数型存储的精度丢失
浮点数型存储有一个天生的缺点:可能发生精度丢失。
根本原因就是:在数学上,有限的十进制数字的二进制形式可能是无限的。
故而某些浮点数是无法精确保存的。
详见文章:C语言中数据类型的规格与截断、补长
并且衍生出一个浮点数比较的结论:如果两float
类型数据的差值小于最小精度位(小数第六位),则这两个数据相等。详见:浮点值的比较
4、大小端字节序存储
我们知道,变量与变量在内存中的存储顺序是由定义顺序与所处内存区域决定(例如在主函数中先后定义a
,b
两个变量,由于这两个变量存放在栈区,故a
变量存放于高地址,b
变量存放于低地址),这是变量与变量之间的存储顺序。
而大小端字节序存储指的是单个变量内部,字节与字节之间的两种存储顺序。
大端字节序存储指的是:
单个数据内部,数据的低位字节内容保存在内存的高地址处,而数据的高位字节内容保存在内存的低地址处。
小端字节序存储指的是:
单个数据内部,数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容保存在内存的高地址处。
有大小端模式之分的原因:
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit
位,但是在C语言中除了8bit
的char
之外,还有16bit
的short
型,32bit
的long
型(这个要看具体的编译器),另外,对于位数大于8位
的处理器,例如16位
或者32位
的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如:一个16bit
的short
型变量x
,在内存中的地址为0x0010
,x
的值为0x1122
,那么0x11
为高字节,0x22
为低字节。对于大端模式,就将0x11
放在低地址0x0010
中,0x22
放在高地址0x0011
中。小端模式,刚好相反。我们常用的X86
结构是小端模式,而KEIL C51
则为大端模式。很多的ARM
,DSP
都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。