二进制-信息的表示与处理《深入理解计算机系统》
二进制是计算机的基石,一切的逻辑,计算都是基于二进制的基础上,来进行的。
为什么计算机选用,二进制而不是十进制,十不是更合乎常理吗。
作为一个系统,往往要求稳定,抗干扰能力强,二进制刚好满足这个条件。
电门只有开和关,二进制,只有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)¬_a;
15 int t2= (a¬_a)|(b¬_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位,正溢出
码无止境

浙公网安备 33010602011771号