目录
2.1数制与编码
2.1.1进位计数制及其相互转换
在计算机系统中,我们常用的进位计数制有二进制、八进制、十进制和十六进制。不同的进位计数制有着各自的特点和用途,了解它们之间的相互转换,能够帮助我们更好地理解计算机数据的表示和处理方式。
-
二进制:基数为2,使用0和1两个数码表示,运算规则简单,适用于逻辑运算,物理实现容易,可靠性高。例如,二进制数1010表示的是
。
-
八进制:基数为8,使用0 - 7八个数码表示。在计算机中的应用不如十六进制广泛,但在某些特定场景下仍有使用。例如,八进制数(35)表示的是
。
-
十进制:基数为10,使用0 - 9十个数码表示,是日常生活中最常用的数制。我们日常使用的数字,如123、45.67等,都是十进制数。
-
十六进制:基数为16,使用0 - 9和A - F(表示10 - 15)十六个数码表示,常用于简化二进制数的表示。例如,十六进制数(0x3A)表示的是
。
不同进位计数制之间的转换:
-
任意进制转十进制:按权展开法,即将各位上的数字乘以该位对应的权值后相加。例如,将二进制数(1101.01)转换为十进制数:
-
-
十进制转任意进制:整数部分使用 “除基取余法”,小数部分使用 “乘基取整法”。例如,将十进制数25.625转换为二进制数:
-
-
二进制、八进制、十六进制之间的相互转换:
-
二进制转八进制:每三位二进制数转换为一个八进制数。例如,二进制数(101101)转换为八进制数,从右往左每三位一组,得到(001\ 011\ 010),分别转换为八进制数为(1\ 3\ 2),所以结果是(132)。
-
二进制转十六进制:每四位二进制数转换为一个十六进制数。例如,二进制数(11011010)转换为十六进制数,从右往左每四位一组,得到(1101\ 1010),分别转换为十六进制数为(D\ A),所以结果是(0xDA)。
-
八进制或十六进制转二进制:将每一位八进制或十六进制数转换为对应的三位或四位二进制数。例如,八进制数(56)转换为二进制数,(5)转换为(101),(6)转换为(110),所以结果是(101110)。
-
2.1.2定点数的编码表示
定点数是一种数值表示方法,它通过固定小数点的位置来表示数值。在计算机中,定点数通常分为有符号和无符号两种表示方式。
-
有符号定点数:使用最高位作为符号位(0表示正数,1表示负数),其余位表示数值。例如,一个(8)位的有符号定点数(00001111)表示的是正数(15),而(10001111)表示的是负数(-15)(原码表示)。
-
无符号定点数:所有位都用于表示数值,没有符号位。例如,一个(8)位的无符号定点数(11111111)表示的是(255)。
定点数的编码方式:
-
原码:原码是最直观的机器数表示法,用机器数的最高位表示该数的符号,其余位表示数的绝对值。正数的原码符号位为(0),其余位为数值本身;负数的原码符号位为(1),其余位为数值取反加一(实际上是该数绝对值的二进制表示)。例如,(+5)的原码表示为(00000101)(假设使用(8)位表示),(-5)的原码表示为(10000101)。需要注意的是,原码中0的表示不唯一,有+0(00000000)和-0(10000000)之分。
-
反码:正数的反码与原码相同;负数的反码是在原码的基础上,符号位不变,其余位取反。例如,(-5)的反码表示为(11111010)。反码中(0)的表示也不唯一,与原码相同。
-
补码:正数的补码与原码相同;负数的补码是在原码的基础上,符号位不变,其余位取反后加(1)。补码中(0)的表示唯一,解决了原码中(0)表示不唯一的问题。例如,(-5)的补码表示为(11111011)。补码还具有模运算的性质,即两个补码数相加,若最高位(符号位)有进位,则舍去该进位,结果仍为补码形式,且正确表示了两个数相加的结果。
-
移码:移码是在补码的基础上,将符号位取反得到。即正数的移码为其补码符号位变(0),负数的移码为其补码符号位变(1)。移码主要用于浮点数的阶码表示,以便于判断浮点数的阶码大小(移码大则阶码大,真值也大)。移码中(0)的表示也是唯一的。
2.1.3整数的表示
在计算机中,整数可以用无符号整数和有符号整数来表示。
-
无符号整数:所有位都用于表示数值,没有符号位。例如,一个(8)位的无符号整数,其表示范围是(0)到(2^8 - 1),即(0)到(255)。
-
有符号整数:使用最高位作为符号位,其余位表示数值。在有符号整数的表示中,常用的编码方式是补码。以(8)位有符号整数为例,其表示范围是(-2^7)到(2^7 - 1),即(-128)到(127)。例如,(-128)的补码表示为(10000000),(127)的补码表示为(01111111)。
2.1.4 C语言中的整数类型及类型转换
在(C)语言中,有多种整数类型,不同的整数类型占用不同的内存空间,其表示范围也不同。
-
常见的整数类型:
-
类型转换:在(C)语言中,当不同类型的整数进行运算时,会发生类型转换。类型转换分为隐式类型转换和显式类型转换。
- 隐式类型转换:编译器自动进行的类型转换,遵循一定的规则。一般来说,较小类型会转换为较大类型,以避免数据丢失。例如,当一个(char)类型和一个(int)类型进行运算时,(char)类型会被转换为(int)类型。
#include <stdio.h>
int main() {
char c = 'A';
int i = 10;
int result = c + i;
printf("result: %d\n", result); // 输出result: 75
return 0;
}
在上述代码中,字符'(A)'的(ASCII)码值为(65),(char)类型的(c)在与(int)类型的(i)进行加法运算时,会被隐式转换为(int)类型,然后进行计算。
//显式类型转换:也称为强制类型转换,通过使用类型转换运算符来显式地将一个类型转换为另一个类型。例如:
#include <stdio.h>
int main() {
double d = 3.14;
int i = (int)d;
printf("i: %d\n", i); // 输出i: 3
return 0;
}
在上述代码中,使用((int))将(double)类型的(d)强制转换为(int)类型,小数部分被截断。
2.2运算方法和运算电路
2.2.1基本运算部件
在计算机中,基本的运算部件包括加法器、减法器、乘法器和除法器等。这些运算部件是实现数据运算的基础,它们通过电子逻辑电路来实现各种运算操作。
-
加法器:加法器是最基本的运算部件之一,用于实现两个数的加法运算。在计算机中,常用的加法器是二进制加法器,它基于二进制的运算规则进行计算。例如,半加器只能实现两个一位二进制数的加法,不考虑来自低位的进位;全加器则可以实现两个一位二进制数的加法,并考虑来自低位的进位。多位加法器可以通过多个全加器级联实现。
-
减法器:减法运算可以通过补码的加法来实现。在计算机中,通常将减法转换为加法进行处理。例如,(A - B)可以转换为(A+(-B)),其中(-B)是(B)的补码。
-
乘法器:乘法运算可以通过重复的加法和移位操作来实现。例如,在原码一位乘法中,通过逐位判断乘数的每一位,若为(1),则将被乘数加到部分积中,然后将部分积和乘数右移一位;若为(0),则直接将部分积和乘数右移一位。经过若干次操作后,得到最终的乘积。
-
除法器:除法运算可以通过重复的减法和移位操作来实现。例如,在原码除法中,通过比较被除数和除数的大小,若被除数大于等于除数,则从被除数中减去除数,并将商的对应位置(1),然后将被除数和除数左移一位;若被除数小于除数,则商的对应位置(0),同样将被除数和除数左移一位。经过若干次操作后,得到商和余数。
2.2.2定点数的移位运算
定点数的移位运算分为算术移位和逻辑移位。
-
算术移位:对于有符号数的移位运算,算术移位保持符号位不变,仅对数值部分进行移位操作。
- 左移:在算术左移时,低位补(0),高位舍弃。例如,对于一个(8)位的有符号整数(00000110)(表示(6)),左移一位后得到(00001100)(表示(12)),相当于将原数乘以(2)。但如果左移过程中高位舍弃的是(1),则会发生溢出。
- 右移:算术右移时,高位补符号位,低位舍弃。例如,对于一个(8)位的有符号整数(00000110)(表示(6)),右移一位后得到(00000011)(表示(3)),相当于将原数除以(2)。对于负数,如(10000110)(表示(-118)),右移一位后得到(11000011)(表示(-59))。
-
逻辑移位:对于无符号数的移位运算,逻辑移位不考虑符号位,左移时低位补(0),右移时高位补(0)。例如,对于一个(8)位的无符号整数(00000110),左移一位后得到(00001100),右移一位后得到(00000011)。
2.2.3定点数的加减运算
定点数的加减运算在计算机中是通过补码来实现的。
- 加法运算:两个补码表示的数相加,只需要将它们的对应位相加即可。如果最高位(符号位)有进位,则舍去该进位。例如:
2.3浮点数的表示与运算(难点)
2.3.1浮点数的表示
浮点数是一种用于表示实数的方法,它能表示范围更大的数值。在计算机中,浮点数通常采用类似于科学计数法的形式表示,即,其中(M)为尾数,(E)为阶码。
以IEEE 754标准为例,这是目前最常用的浮点数表示标准。该标准规定了单精度浮点数(32位)和双精度浮点数(64位)的格式。
单精度浮点数
由1位符号位(S)、8位阶码(E)和23位尾数(M)组成。符号位(S)表示数的正负,(0)表示正数,(1)表示负数。阶码(E)采用移码表示,其偏移量为(127),即实际的指数值为(E - 127)。尾数(M)是一个纯小数,且规定小数点前隐含一个(1)(因为任何非零数都可以表示为的形式),所以实际存储的尾数是小数点后的部分。
例如,对于单精度浮点数(1.25),转换为二进制是(1.01_{2}),写成规格化形式为,在计算机中的表示为:
例如:
#include <stdio.h>
struct {
char a; // 占用1个字节
int b; // 占用4个字节
} s;
int main() {
printf("Size of struct s: %zu\n", sizeof(s));
return 0;
}
在上述代码中,(struct\ s)中(a)和(b)的总大小理论上是(1 + 4=5)个字节,但由于对齐存储的要求,实际输出的(sizeof(s))可能是(8)个字节。这是因为编译器会在(a)后面填充(3)个字节,使得(b)的起始地址是(4)的倍数,以满足对齐要求。
双精度浮点数
由1位符号位(S)、11位阶码(E)和52位尾数(M)组成。阶码(E)的偏移量为(1023),实际指数值为(E - 1023)。同样,尾数(M)也是纯小数,小数点前隐含(1)。双精度浮点数能表示的数值范围更广、精度更高。
2.3.2浮点数的加减运算
浮点数的加减运算比定点数复杂,一般需要经过以下几个步骤:
对阶
将两个浮点数的阶码调整到相同。通过比较两个数的阶码大小,将阶码小的数的尾数右移,每右移一位,阶码加(1),直到两个数的阶码相等。
例如,计算,先将
的尾数右移两位,阶码加(2),变为
。
尾数运算
对阶后,将两个数的尾数进行加减运算。这里采用定点数的加减运算方法,如补码运算。
假设上述两个数的尾数在对阶后分别为(1.01)和(0.015625),进行加法运算:(1.01 + 0.015625=1.105625)。
规格化
对运算结果进行规格化处理,使其符合浮点数的规格化形式(即尾数的小数点前为(1))。
如果运算结果的尾数大于等于(2),需要将尾数右移一位,阶码加(1);如果尾数小于(1),需要将尾数左移,直到尾数的小数点前为(1),同时阶码相应减(1)。例如,上述结果(1.105625)已经是规格化形式。
舍入
由于计算机中尾数的位数有限,可能需要对运算结果进行舍入处理,以保证精度。常见的舍入方法有截断法(直接舍去多余的位)、恒置(1)法(不管多余的位是什么,直接在最低位加(1))和(0)舍(1)入法(多余的位最高位为(0)则舍去,为(1)则在最低位加(1))等。
溢出判断
检查运算结果的阶码是否超出了浮点数所能表示的范围。如果阶码超出范围,则发生溢出。若阶码大于最大阶码,称为上溢;若阶码小于最小阶码,称为下溢。上溢通常作为错误处理,而下溢时一般将结果置为机器零。
2.3.3 C语言中的浮点数类型
在C语言中,浮点数类型主要有float(单精度浮点数)和double(双精度浮点数)。
float类型
通常占用(4)个字节(32位,其表示范围约为到
,有效数字为(6 - 7)位。
例如:
#include <stdio.h>
int main() {
float f = 3.14159f; // 注意后面的f,表示这是一个单精度浮点数
printf("f: %f\n", f);
return 0;
}
在上述代码中,定义了一个(float)类型的变量(f)并初始化为(3.14159),由于(float)类型的精度限制,输出结果可能会有一定的误差。
double类型
占用(8)个字节((64)位),表示范围约为到
,有效数字为(15 - 17)位。
例如:
#include <stdio.h>
int main() {
double d = 3.141592653589793;
printf("d: %lf\n", d);
return 0;
}
这里定义了一个(double)类型的变量(d),它能更精确地表示小数。
2.3.4数据的大小端和对齐存储
大小端存储
在计算机中,数据在内存中的存储顺序有大端(Big - Endian)和小端(Little - Endian)两种方式。
大端存储
数据的高位字节存于低地址,低位字节存于高地址。例如,对于一个(16)位的整数(0x1234),在大端存储方式下,内存中的存储顺序为:低地址存放(0x12),高地址存放(0x34)。
小端存储
与大端存储相反,数据的低位字节存于低地址,高位字节存于高地址。对于整数(0x1234),在小端存储方式下,低地址存放(0x34),高地址存放(0x12)。不同的计算机系统可能采用不同的存储方式,这在进行数据传输或跨平台编程时需要特别注意。例如,在网络通信中,通常采用大端存储方式(网络字节序),以保证数据在不同系统间的正确传输。
对齐存储
为了提高数据访问的效率,计算机通常会对数据进行对齐存储。对齐存储规定了数据在内存中的起始地址必须是该数据类型大小的整数倍。
例如,在(32)位系统中,(int)类型(通常占用(4)个字节)的变量在内存中的起始地址应该是(4)的倍数。如果不满足对齐要求,可能会导致数据访问速度变慢,甚至在某些系统中会出现错误。
- 减法运算:减法运算通过将减数取补码后与被减数相加来实现。例如,计算 6 - 4,即 6 + (-4):
- 在进行定点数加减运算时,需要注意溢出问题。当运算结果超出了定点数所能表示的范围时,就会发生溢出。例如,对于一个 8 位的有符号整数,其表示范围是 - 128 到 127,如果运算结果超出这个范围,就会出现溢出。判断溢出的方法有多种,例如可以通过比较运算前后符号位的变化来判断是否发生溢出。
-
2.2.4定点数的乘除运算
乘法运算 - 原码一位乘法:原码一位乘法的基本思想是通过逐位判断乘数的每一位,若为1,则将被乘数加到部分积中,然后将部分积和乘数右移一位;若为0,则直接将部分积和乘数右移一位。经过若干次操作后,得到最终的乘积。
- 符号位(S = 0)(正数)
- 阶码(E = 0 + 127=127),转换为二进制是(01111111)
- 尾数(M)为(01)(小数点后的部分),补齐23位为(01000000000000000000000)。 最终的32位表示为(00111111101000000000000000000000)。