二进制-信息的表示与处理《深入理解计算机系统》

二进制是计算机的基石,一切的逻辑,计算都是基于二进制的基础上,来进行的。
 
为什么计算机选用,二进制而不是十进制,十不是更合乎常理吗。
 
作为一个系统,往往要求稳定,抗干扰能力强,二进制刚好满足这个条件。
 
电门只有开和关,二进制,只有0和1,刚好对应,不是0就是1,非此即彼,简单,易于控制。
 

 进制

二进制

二进制(binary)是在数学和数字电路中指以2为基数的记数系统,是以2为基数代表系统的二进位制。这一系统中,通常用两个不同的符号0(代表零)和1(代表一)来表示。二进制是逢2进位的进位制。
0001 [1] + 0001 [1] = 0010 [2]
 
比特: 比特也是二进制数字中的位,信息量的度量单位,为信息量的最小单位。
 
一个字节,由8个位组成,其范围是0000 00002 ~ 1111 11112 ,十进制的范围是 010 ~ 25510 ,二进制的表示方法过于冗长,因此为了表示简单引入了十六进制。
 

十六进制 

十六进制在数学中是一种逢16进1的进位制。一般用数字0到9和字母A到F(或a~f)表示,其中:A~F表示10~15,这些称作十六进制数字
 
十六进制(“hex”),使用数字 0 ~ 9 以及 字符 A ~ F 表示16个可能的值。
十六进制
A
B
C
D
E
F
十进制
10
11
12
13
14
15
二进制
1010
1011
1100
1101
1110
1111
在C语言中,以0x或0X开头的数字常量被认为是十六进制的值。
 

字长

每台计算机都有一个字长(word size),指明指针数据的标称大小(nominal size)。因为虚拟地址以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小,也就是说对一个字长位w位的机器而言,虚拟地址的范围为 0 ~ (2^w -1),程序最多访问2^w个字节。
gcc -m32 prog.c 可运行在32或64位机器上
gcc -m64 prog.c 可运行在64位的机器上
我们将程序称为 “32位程序”,或 “64位程序”,区别在于该程序是如何编译的,而不是起运行的机器类型。
查看字长: uname -a(Mac)
 
当我们能够表示一个数的时候,往往需要对数据进行计算,对于二进制数,我们可以进行布尔代数运算。布尔代数包括,非(~),且(&),或(|),异或(^)四种。
 

二进制运算

布尔运算逻辑

非:布尔运算 ~对应逻辑运算NOT,在命题逻辑中用﹁ 表示。当P不是真时,非P为真,反之亦然
且:布尔运算 & 对应逻辑运算 AND,在命题中用∧ 表示。 当P和Q都为真时即P=1且Q=1时,P∧Q为真
或: 布尔运算 |对应逻辑运算OR,在命题中用∨表示。当P或Q为真时,即P=1或Q=1时,P∨Q为真
异或:布尔运算^对应逻辑运算异或,在命题中用⊕表示。当P或Q为真,但不同时为真时,P⊕Q为真
对任何值a来说 a^a =0.
 
C语言中位运算
int a = 5; //0101
int b = 3; //0011
C表达式
二进制表达式
二进制结果
十进制结果
~a
~[0101]
[1010]
-8+2 =-6
a | b
[0101] | [0011]
[0111]
4+2+1=7
a & b
[0101] & [0011]
[0001]
1
a ^ b
[0101] ^ [0011]
[0110]
4+2=6
 
 
 1 #include <stdio.h>
 2 
 3 int main(){
 4     int a = 5; //0101
 5     int b = 3; //0011 
 6     
 7     int not_a = ~a;
 8     int a_or_b = a|b;
 9     int a_and_b = a&b;
10     int a_e_or_b = a^b;
11     int a_e_or_a = a^a;
12     printf("a is %d ,b is %d \n not a is  %d \n a or b is %d \n a and b is %d \n a exclusive_or b is %d \n a e_or_a is %d \n",a,b,not_a,a_or_b,a_and_b,a_e_or_b,a_e_or_a);
13     
14     int t1= (a|b)&not_a;
15     int t2= (a&not_a)|(b&not_a);
16     printf("t1 is %d \n t2 is %d",t1,t2);
17     return 0;
18 } 
 
Tips
Tips1 :布尔运算与整数运算关系
0101 & 0011 = 0001
a & (b| c) = (a & b)| (a & c)
a | (b & c) = (a | b) & (a | c)
见代码14,15,16行
 
Tips2: 任何数的对自己的异或都为0
对任何 值a^a = 0,即1^1=0 0^0=0 。
(a^b)^a=b
见代码11行
 
Tips3:位向量与集合关系
位向量 a =[01101001] 表示集合 A={0,3,5,6}
b =[01010101] 表示集合 B={0,2,4,6}
a & b = [01000001] , A和B的交集 A∩B = {0,6}
 

逻辑运算

逻辑运算符 || , && , ! 对应命题逻辑中的 OR , AND ,和 NOT。逻辑运算认为所有的非零的参数都表示TRUE,而参数0表示FALSE。返回 1 表示TRUE , 返回 0 表示FALSE。
int a = 5; //0101
int b = 3; //0011
表达式
结果
!a
0x00
a||b
0x01
a&&b
0x01
 
 
位运算与逻辑运算区别
区别1 : 逻辑返回只有TRUE,或 FALSE ,所有非零表示TRUE,参数零,表示FALSE
区别2 : 如果第一个参数求值就能表达结果,那么逻辑运算符就不会对第二个参数求值
 
对二进制,我们不仅可以进行位运算,还可以对其进行移位运算,将数据整体向左移动,或者向右移动,从而达到使原数据放大或缩小2^k(为移动的位数)倍。
 

移位运算

X : [Xw-1,Xw-2 , ... , X0]
逻辑左移 :C表达式 X<<k 会生成一个值,其位表示为[Xw-k-1 , Xw-k-2 , ... , X1 , X0 , 0, 0 , ... , 0], X向左移动k位,丢弃最高的k位,并在右端补k个0,数据放大2^k 倍。
如:0000 0101[5] << 2 得到 0001 0100[20] 即 5 * 2^2 = 20
 
逻辑右移 :C表达式X>>k ,结果表达式为[0 , 0 , ... , Xw-1 , Xw-2 , ... , Xk] , 左端补k个0,数据缩小2^k 倍。
如:0000 0101[5] >> 2 得到 0000 0001[1] 即 5 / 2^2 = 1 ,本来值为1.25,但是保留整数为1
 
算术右移 :C表达式X>>k , 算术右移是在左端补k个最高有效位的值。
如 : 1000 1011 >> 2 得到 1110 0010
 1 #include <stdio.h>
 2 
 3 int main(){
 4     int a = 3; //0011
 5     int b = -1; //1111
 6     unsigned c = 9; // 1001
 7     int a_left_3 = a << 3 ; // 0001 1000
 8     int a_right_3 = a >> 3; // 0000
 9     int b_left_3 = b << 3 ; // 1000
10     int b_right_3 = b >> 3; // 1111
11     int c_left_3 = c<<3; // 0100 1000
12     int c_right_3 = c>>3; // 0001
13     printf("a move left 3 is %d \n a move right 3 is %d \n b move left 3 is %d \n b move right 3 is %d \n c left 3 is %d \n c right 3 is %d",a_left_3,a_right_3,b_left_3,b_right_3,c_left_3,c_right_3);
14     return 0;
15 }
 
Note:
Note1: 许多机器,默认是算术位移
Note2:无符号数,必须是逻辑位移
Note3:如果一个数由w位组成,k>=w ,许多机器 计算 k mod w ,取余,移动对应的位数
X(十六进制)
X(二进制)
X<<3(二进制)
X<<3(十六进制)
X>>2(逻辑二进制)
X>>2(逻辑十六进制)
X>>2(算术二进制)
X>>2(算术十六进制)
0xC3
1100 0011
0001 1000
0x18
0011 0000
0x30
1111 0000
0xF0
0x75
0111 0101
1010 1000
0xA8
0001 1101
0x1D
0001 1101
0x1D
0x87
1000 0111
0011 1000
0x38
0010 0001
0x21
1110 0001
0xE1
 

原码,反码,补码

为什么使用补码

现在的计算机在计算时,基本都是使用的补码来进行的,除了补码外还有,原码,反码,但原码和反码对 0的表示有 +0,-0,这不利于计算,而补码不会存在这种问题。
原码 [10000000....00] =-0
反码 [111111111111...11] =-0
 

原码

最高位表示符号位,0表示正数,1表示负数,其余位表示为整数的二进制数
+1 : 0000 0001
-1 : 1000 0001
 

反码

正数的反码与正码相同,负数的反码在正码的基础上,对初符号位外其他位取反
+1 : 0111 1110
-1 : 1111 1110
 

补码

正数的补码是其本身,负数的反码在其原码的基础上,符号位不变,其余各位取反,最后加1(反码的基础上加1)
+1 : 0111 1111
-1 : 1111 1111
 
补码目的,简化运算,将符号位也加入到运算中来。
(+1)+(-1)= [0000 0001]原 + [1000 0001]原 ?
(+1)+(-1)= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = - 0
(+1)+(-1)= [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原 = 0
 
unicode 字符串中,每个字符四个字节(32位)
UTF-8 ,ASCII 每个字符1个字节系列
 
二进制,可以表示有符号数,也可表示无符号数,它们之间可以进行相互转换。
 

二进制,补码,无符号数

有符号数,无符号数,C语言的取值范围
Type Name
Bytes
Other Names
Range of Values
int
4
signed
-2,147,483,648 to 2,147,483,647
unsigned int
4
unsigned
0 to 4,294,967,295
__int8
1
char
-128 to 127
unsigned __int8
1
unsigned char
0 to 255
__int16
2
short, short int, signed short int
-32,768 to 32,767
unsigned __int16
2
unsigned short, unsigned short int
0 to 65,535
__int32
4
signed, signed int, int
-2,147,483,648 to 2,147,483,647
unsigned __int32
4
unsigned, unsigned int
0 to 4,294,967,295
__int64
8
long long, signed long long
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
unsigned __int64
8
unsigned long long
0 to 18,446,744,073,709,551,615
bool
1
none
false or true
char
1
none
-128 to 127 by default
 
0 to 255 when compiled by using /J
signed char
1
none
-128 to 127
unsigned char
1
none
0 to 255
short
2
short int, signed short int
-32,768 to 32,767
unsigned short
2
unsigned short int
0 to 65,535
long
4
long int, signed long int
-2,147,483,648 to 2,147,483,647
unsigned long
4
unsigned long int
0 to 4,294,967,295
long long
8
none (but equivalent to __int64)
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
unsigned long long
8
none (but equivalent to unsigned __int64)
0 to 18,446,744,073,709,551,615
enum
varies
none
 
float
4
none
3.4E +/- 38 (7 digits)
double
8
none
1.7E +/- 308 (15 digits)
long double
same as double
none
Same as double
wchar_t
2
__wchar_t
0 to 65,535
 
 
 
 

二进制,补码,无符号数转换

给定一个(整数对应的)长度为 [公式] 的二进制串 [公式] (下标从0开始),其对应的整数为 [公式] ,在可表示的范围内。

符号说明:

[公式] :长度为[公式]的二进制串[公式]转化为补码表示的整数(Binary to two's complement).(即假设 [公式] 是 [公式] 的补码,计算对应的整数)

[公式] :长度为[公式]的二进制串[公式]转化为无符号整数.(即假设 [公式] 是无符号整数[公式]的二进制表示,计算对应的无符号整数[公式])

[公式]

二进制转补码

二进制转补码,最高位取反

 [公式]

如:1001 = -(1*8 )+ 0*2 + 0*1 + 1*1 = -7
 

二进制转无符号数

 [公式]

如 : 1001 -> 1001u = 1*8 + 0*2 + 0*1 + 1*1 = 9

 

补码转化为无符号数

[公式]

如 :1001 (x<0) -> 1001u=8+1= -7 +2^4=9

       0110 (x>0) -> 0110u =4+2= 6

 

无符号数转化为补码

[公式]

如:0110u (x≤7) -> 0110 = 4+2 = 6

      1101u (x>7) -> 1101=-8+4+1 = 13 - 16 = -3 

 

无符号的最大值,与有符号的 -1 ,有一样的位数
无符号数 1111u =15
有符号数 1111 = -1
UMaxw + 1 = 2^w
 
 

强转,数据扩展与截取

强转

当一个有符号的数与一个无符号的数进行计算时,会将有符号数转换为无符号数,然后再进行计算。
使用无符号数时要特别注意
 

扩展一个数字的位表示

无符号的位扩展:

将一个无符号数转化为一个更大的数据类型,只需简单的在开头加0,这种运算叫做0扩展。
U = [Uw-1,Uw-2,...U2,U1,U0]
U’ = [0,0,0...,0,Uw-1,Uw-2,...,U2,U1,U0]
如 : 将无符号1001 扩展4位
1001 -> 0000 1001
 

补码数符号扩展

将一个补码转换喂更大的数据类型,在前面添加最高位的值
T = [Tw-1,Tw-2,...T2,T1,T0]
T’ = [Tw-1,Tw-1,Tw-1,...,Tw-1,Tw-1,Tw-2,...,T2,T1,T0]
如:将补码1001 扩展4位
1001 -> 1111 1001
 

截断数字

截断无符号数/截断补码数 是相同的
将一个w位的数 X=[Xw-1,Xw-2,...,X2,X1,X0],截断为k位数字时,会丢弃高位w-k位,得到
X1 = [Xk-1,Xk-2,...,X2,X1,X0]
 

溢出

无符号数溢出

如果一个数最多只有w位 ,无符号数范围是 [0,2^w -1] , 如果两个数的和大于2^w -1 ,那么就属于溢出
如一个数只有4位
1101 + 1101 得到 11010 ,超过4位,溢出
 

补码溢出

如果一个数最多只有w位,补码的范围是[-2^(w-1),2^(w-1)-1],如果两个数的和小于-2^(w-1),则是负溢出,如果两个数的和大于2^(w-1)-1 ,则是正溢出。
如果一个数只有4位
1000 + 1000 得到 10010 ,超过4位,负溢出
0111 + 0111 得到 01110 ,超过4位,正溢出
 
 
posted @ 2021-05-31 18:50  Leoli20  阅读(871)  评论(0)    收藏  举报