1.整形变量在内存中的存储
-
原码、反码、补码
计算机中整数都是以二进制进行存储的。其中有三种表示方法:原码,反码与补码。他们分为符号位、数值位两部分。计算机中存储的是补码形式。
规定符号位用
0
表示正,用1
表示负。
- 原码:直接将二进制按照正负数的形式翻译成二进制即可。
- 反码:原码符号位不变,其它位依次按位取反。
- 补码:反码 末位 +1 得到补码。
补码转换成原码:对补码进一步求补码,即符号位不变,数值位取反,末位再加1。
规定正数的原码、反码、补码相同。负数的三码之间转换使用上面的方法。为什么计算机中存储的是补码形式呢?
使用补码形式,可以将符号位和数值域统一处理。同时加法和减法也可以统一处理(CPU)只有加法器,此外补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
示例代码:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i = -1;
int j = 10;
int ret = i + j;
printf("i = %d\n",ret);
exit(0);
}
首先,需要确定该编译器中内存对数据的存储方式是大端存储还是小端存储
//定义十六进制数a
int a = 0x11223344 // 数据地位:高位 --> 低位
0x0000008B34FEFA24 44 // 内存低地址(存储低位数据)
0x0000008B34FEFA25 33 // |
0x0000008B34FEFA26 22 // V
0x0000008B34FEFA27 11 // 内存高地址(存储高位数据)
可见该内存对数据的存储是小端存储。
// i = -1 的地址以及在内存中的表示 :
0x000000F1BD7CFA64 ff // bin : 1111 1111
0x000000F1BD7CFA65 ff // bin : 1111 1111
0x000000F1BD7CFA66 ff // bin : 1111 1111
0x000000F1BD7CFA67 ff // bin : 1111 1111
ff ff ff ff -> 1111 1111 1111 1111 1111 1111 1111 1111 (补码形式)
(首位符号位) 1000 0000 0000 0000 0000 0000 0000 0000 (反码形式)
1000 0000 0000 0000 0000 0000 0000 0001 (原码形式)
转换成十进制为 1 。
// j = 10 的地址以及在内存中的表示 :
0x000000F1BD7CFA84 0a // bin : 0000 1010
0x000000F1BD7CFA85 00 // bin : 0000 0000
0x000000F1BD7CFA86 00 // bin : 0000 0000
0x000000F1BD7CFA87 00 // bin : 0000 0000
00 00 00 0a -> 0000 0000 0000 0000 0000 0000 0000 1010 (补码形式)
正数的三码相同,因此转换成十进制为 10.
// ret 的地址以及在内存中的表示 :
0x000000F1BD7CFAA4 09 // bin : 0000 1001
0x000000F1BD7CFAA5 00 // bin : 0000 0000
0x000000F1BD7CFAA6 00 // bin : 0000 0000
0x000000F1BD7CFAA7 00 // bin : 0000 0000
00 00 00 09 -> 0000 0000 0000 0000 0000 0000 0000 1001
ret = 9 是正数,三码相同,转换成十进制是 10.
存储示意图:
计算机的位数也叫字长,是指处理器一次运算所能处理的二进制数的位数。
2.浮点数在内存中的存储
根据国际标准IEEE(电气和电子工程协会),任意一个二进制浮点数V可以表示成下面的形式:
(-1)^S \times M \times 2^E
(-1)^s
表示符号位,当s=0
,V
为正数;当s=1
,V
为负数。M
表示有效数字,大于等于1,小于2。2^E
表示指数位。
浮点数转化为十进制,为各个位与位权乘积之和。以小数点为分界线,小数点前的位权依次是 2^0, 2^1 ···,小数点后的位权依次为2^-1, 2^-2···。详见下图:
以此类推。0次幂前面的位的位权 按照 1 次幂、2 次幂……的方式递增,0 次幂以后的位的位权按照-1 次幂、-2次幂……的方式递减。这一规律并不仅限于二进制数,在十 进制数和十六进制数中也同样适用。
例如5.5
,因为二进制(5) = 0101
,根据上面规则倒推:因为0.5 = 1 * 2^-1
,则可知,(二进制)5.5 = 0101.1
,写成科学计数法形式为:
\(1.011 \times 2^2 = (-1)^0 \times 1.011 \times 2^2\)
可知:S = 0, M = 1.011, E = 2
.
所以它是如何进行存储的呢?
IEEE 754对有效数字(尾数部分)M和指数(部分)E,还有一些特别规定:
1≤M<2
,也就是说,M
可以写成1.xxxxxx
的形式,其中xxxxxx
表示小数部分。 在计算机内部保存M
时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx
部分。- 比如保存
1.01
的时候,只保存01
,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
至于指数E则比较复杂:复杂在E为一个无符号整数(unsigned int),但仍要表示正负。
- 如果E为8位,它的取值范围为0~255;
- 如果E为11位,它的取值范围为0~2047。
但是,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数:
- 对于8位的E,这个中间数是127;
- 对于11位的E,这个中间数是1023。
比如,
2^10
的E
是10
,所以保存成32位浮点数时,必须保存成10+127=137
,即10001001
。
那么为什么内存对浮点数的存储要取中间数呢?为什么要取127、1023?
指数部分用的则是“EXCESS系统表现”,即通过将指数部分在相应精度下所能表示的最大范围的中间值设为0,使得负数就不需要单独进行表示,也就时用相对为负来表示负指数的情况的情况。就好比用1 ~ 13 这组数表示负数,其范围以确定,就把中间值7当作0,比如,10就表示+3,4表示-3.这个规则说的就是EXCESS系统. 因此:
- 对于单精度浮点数类型数据,E最大值为
1111 1111 -> 128+64+32+16+8+4+2+1 = 255
,中间值为127
; - 对于双精度浮点数类型数据,E最大值为
0111 1111 1111 -> 1024+512+256+128+64+32+16+8+4+2+1 = 2047
,中间值为1023
;
示例一:
float f = 5.5;
二进制形式:101.1
正则表达式:(-1)^0 * 1.011 * 2^2
符号值:S=0
尾数值:M=1.011
指数值:E=2 + 127 = 129 //实际在内存中存储的值
0 10000001 01100000000000000000000 //存储的时候,有效数字省略小数点前的1,之后的有效数字在内存中从高为开始依次存储(011存进去后因为最终有23位所以后面补0)。
转换成内存空间中以十六进制保存为:
- 以8bit为单位划分:
0100 0000 1011 0000 0000 0000 0000 0000
- 以8bit为单位实现小端存储:
0000 0000 0000 0000 1011 0000 0100 0000
- 十六进制:
0 0 0 0 b 0 4 0
- 即 00 00 b0 40
示例二:
float g = 0.75;
二进制形式:因为:0.75 = 0.5 + 0.25 = 2^-1 + 2^-2 -> 所以:0.11
正则表达式:(-1)^0 * 1.1 * 2^-1
符号值:S=0 //0
尾数值:M=1.1 //100000000000000000000000
指数值:E=-1 + 127 = 126 //实际在内存中存储的值: 01111110
0 01111110 100000000000000000000000
转换成内存空间中以十六进制保存为:
- 以8bit为单位划分:
0011 1111 0100 0000 0000 0000 0000 0000
- 以8bit为单位实现小端存储:
0000 0000 0000 0000 0100 0000 0011 1111
- 十六进制:
0 0 0 0 4 0 3 f
- 即 00 00 40 3f
你滴明白?明白!