计算机程序基础教程(01):计算机运行原理
【计算机的基础原理】
计算机的运行是以逻辑电路为基础的,首先我们复习一下初中物理电路知识,了解逻辑电路的基础原理。
在电路中放置一个可以移动的衔铁,改变衔铁的位置可以控制电路连接或断开。衔铁平时依靠弹性呈断开电路状态,在衔铁附近放置一个电磁铁,电磁铁通电产生磁性,吸引衔铁移动,将电路闭合,电路即可通电。这种通过电磁铁控制电路开关的器件称为电磁继电器。
当然芯片内部不可能将电磁铁放进去,芯片内的器件是用纳米级工艺刻出来的,至于怎么刻,那是半导体物理学的研究范畴,我们无需关心。
使用继电器可以制作出三种基本的逻辑运算单元:与、或、非。
与运算,电路中放置两个继电器,两个继电器全部通电时,电路通电,可以使用串联电路实现。
或运算,电路中放置两个继电器,两个继电器有一个通电时,电路即可通电,可以使用并联电路实现。
非运算,电路中放置一个继电器,将电路通电状态取反,通电变为不通电,不通电变为通电。

计算机使用带电与不带电两种状态表示二进制数中的0和1,对于不产生进位的二进制加法可以使用或运算实现,分别比较两个数据中相同位置的数字是否至少有一个为1,是的话计算结果此位为1,否则此位为0。

但是加法运算是不可能避免产生进位的,加法器可以首先使用与运算判断两个数字是否都为1,是的话则产生一个进位,否则再进行或运算,完整的加法器计算两个数据相加时,每个二进制位都会计算3个数字相加,分别是两个加数和一个进位值,实现方式很简单。
而设计一个减法器会复杂的多,首先判断一种简单的情况,两个减数相同位置的数字是否相等,若都为0或都为1,则计算结果此位为0;之后再判断相同位置中只有一个1的情况,若被减数为0、减数为1,则被减数需要向高位借位,如果紧邻的高位为0,还需要向更高的位借位,一次减法运算可能会产生多次借位;如果被减数小于减数,计算结果为负,这种情况会更复杂;这些问题导致减法器的设计要比加法器复杂很多,运算速度也要慢得多。
为了增加减法运算的速度,计算机使用加法代替减法。
使用十进制数来说明转换原理:
9 - 3 = 6
可以转换为
9 + 7 = 16,去除十位 = 6
转换原理很简单,因为 -3 与 +7 的结果正好相差10,所以 -3 转换为 +7 之后只需要再减10即可,而减10可以无需计算,只需要不保留进位产生的高位即可。
3和7相加的结果可以产生一个进位,并且低位全为0,这两个数满足相加结果去除高位后等于减10的条件,那3和7就可以称为彼此的补数。
对于 9 - 3 运算,只需要计算出3的补数即可将减法转换为加法,3的补数计算方式为 10 - 3 = 7,我们需要将减法转换为加法,而计算补数时却又产生了另一个减法,这个问题在十进制中很难解决,而在二进制数中却非常的简单。
补数的目的就是让两个数相加后产生一个进位,并且低位全是0,在二进制加法中,如果两个数的每个位都相反,那计算结果的每个位都为1,比如 1010 + 0101 = 1111,这个结果再 +1 就会产生进位,并且低位全是0。
所以我们计算 1010 的补数时,只需要将其每个位取反值,得到 0101,之后再 +1 即可,取反与 +1 可以合并为一个步骤,这样就得到了 1010 的补数为 0110。
● 正负数的表示方式
计算机将一个数据按照是否有正负号分为两种类型:有符号数、无符号数。
对于有符号数,其最高位表示正负号,0表示正,1表示负,若一个数据是负数,则使用它的补码(补数)存储,因为负数本质上是一个减数,直接存储其补码方便运算。
对于无符号数,只能表示正数,其所有的二进制位都用来表示数值。
示例,使用一个8位二进制数表示-5:
最高的符号位设置为1,表示负数,5转换为二进制等于101,负数使用补码存储,根据取反加一的规则,其补码为111 1011,符号位与数值位整合后编码为:1 111 1011
● 计算结果为负数的情况
用10进制数来说明:
5 - 6 = -1
将减法转换为加法,6的补数为4,5 + 4 = 9,显然9并不是 5 - 6 的结果。
当减法结果为负数时,被减数小于减数,此时被减数加减数的补数必定不会产生进位,计算结果也就没有去除高位这一步骤,导致结果比正确值大10。
若想得到正确结果,我们需要自行减去10,9 - 10 = -1, 从而得到 5 - 6 的正确结果。
但是不减10的话,此数据正是减法结果的补数,在计算机中负数是使用补码存储的,所以此时直接存储补数即可。
总结:计算机使用加补码的方式进行减法运算时,若不会产生导致溢出的进位,则CPU会认为计算结果是个负数,将结果的符号位设置为1,之后直接存储计算出来的补码。
● 正0与负0编码重复的问题
对于有符号数来说,每个数据都有正负之分,但是将0分为+0和-0显然不合理,所以计算机使用-0表示有符号数能够表示的最大负值再减1。
比如一个8位的有符号数,去除最高位的符号位,剩余7位用来表示数值,二进制111 1111等于十进制127,所以它的表示范围是 +127 到 -128,其中-128使用-0表示。
那使用-0表示-128是否会引发混乱呢,其实并不会,在8位有符号数中,-128为最小的负数,不能再减,减1等于-129,超出表示范围,发生溢出,这种运算本身就是错误的,肯定会得到错误结果,错上加错也无所谓,但是可以再加,加1等于-127,-127可以使用正常编码方式表示。
【计算机表示小数的方式】
3.1415926 可以使用如下科学计数法表示:
31415926 × 10^7
0.31415926 × 10^-1
计算机中小数的表示方式类似科学计数法,只不过乘方底数由10改为2,原因是二进制数需要乘2才会向高位移动一位,使用科学计数法表示小数的方式也称为浮点表示法。
使用浮点表示法的原因是为了利用其可以移动小数点的特点,将存储器中的每一个位都利用起来,避免造成浪费,同时也可以使用长度有限的存储单元表示长度更大的小数。
若不使用浮点表示法,则我们只能约定小数点在数据中固定的位置,比如一个长度32位的二进制数据,我们可以约定前16位表示整数,后16位表示小数,若小数为0.xxx,则整数位会浪费掉,若小数为xxx.0,则小数位会浪费掉。
● 浮点数的组成部分
浮点数由三部分组成:符号位、阶码、尾数。
符号位,表示小数的正负号。
阶码,表示乘方的指数。
尾数,科学计数法的尾数,它的数据全部用来表示小数位,可以通过改变阶码的值来移动小数点的位置,从而表示整数位不为0的小数。
乘方的底数因为固定为2,所以无需使用任何存储元件存储,这是计算机的一种约定,从而节省存储空间。
● 浮点数各部分的长度以及排列方式
浮点数中符号位占用最高位,之后是阶码,最后是尾数,IEEE754标准规定了三种浮点数类型,分别如下:
单精度浮点数,长度4字节,符号位占1位,阶码长度8位,尾数长度23位。
双精度浮点数,长度8字节,符号位占1位,阶码长度11位,尾数长度52位。
扩展双精度浮点数,长度10字节,符号位占1位,阶码长度15位,尾数长度64位。
● 阶码表示指数的方式
阶码自身没有符号位,这样可以让长度有限的阶码表示更大范围的指数,但指数是需要表示负数的,那没有符号位的阶码如何表示负数呢。
实际上计算机是让指数加一个数值表示阶码,将有符号数的所有数值范围映射到无符号数中,从而避开负数。单精度浮点数加127,双精度浮点数加1023,扩展双精度浮点数加16383,这里将增加的数称为校正值。
以单精度浮点数为例:
若指数用于表示 -127 - 0,则加127后等于 0 - 127,阶码使用 0 - 127 表示指数的 -127 - 0。
若指数用于表示 1 - 128,则加127后等于 128 - 255,阶码使用 128 - 255 表示指数的 1 - 128。
● 浮点数的规格化
如果浮点数用来表示一个整数位为0的十进制小数,则转换为二进制后,小数位的高位可能会有很多0,比如 0.375 转换为二进制为 0.011。
为了节省存储空间,浮点数的尾数部分不存储这些高位的0,空出的存储元件可以存储更多的低位值,从而增加浮点数的精度,由此导致尾数与原值不同的问题,需要通过修改指数的方式来将尾数还原,比如上述的0.011可以这样记录:0.11 × 10^-1。
既然尾数的最高位必须是1,那这个位其实也可以省略,从而又节省一位存储元件,大家都遵循此约定,使用浮点数时再加上这个省略的1即可。
因为最高位的1被省略,尾数的次高位变成了最高位,导致尾数又向高位移动了一位,所以此时需要将指数再减1,还原尾数的原值。
比如0.011,使用浮点表示法应该存储为 0.1 × 10^-2,其中小数位包含一位隐藏的高位1,你也可以认为是 1.1 × 10^-2,只不过整数位的1不会被存储。
实际上只有单精度、双精度浮点数会省略最高位的1,而扩展双精度浮点数最高位1不能省略,这是IEEE754标准的规定。
● 浮点数编码示例
示例1:单精度浮点数 0.625
0.625转换为二进制为0.101,使用浮点表示法时各部分的值如下:
符号位,这里是正数,符号位设置为0。
阶码,因为尾数最高位的1被隐藏,次高位变成了最高位,尾数向高位移动了一位,所以需要将指数设置为-1,还原尾数原值,指数加校正值得出阶码,-1 + 127 = 126,转换为二进制等于 0111 1110。
尾数原值为101,最高位的1被省略,所以尾数存储为01。
最终编码为:0 01111110 01000000000000000000000 ,其中尾数长23位。
示例2:单精度浮点数 -0.375
0.375转换为二进制为0.011,浮点数各部分编码如下:
符号位为1,表示负数。
阶码,尾数首先需要向高位移动一位,去除最高位的0,之后将最高位的1隐藏,尾数总共向高位移动了两位,所以指数应该设置为-2,-2 + 127 = 125,转换为二进制等于 0111 1101。
尾数原值为011,高位的0和1不存储,实际存储为1。
最终编码为:1 01111101 10000000000000000000000
【计算机硬件的基本结构】
计算机主要由如下4个部分组成:
中央处理器,就是大家经常说的CPU,起控制和运算功能。
存储器,用于存储二进制数据。
输入设备,用于向CPU输入数据,比如键盘、鼠标。
输出设备,接收CPU发出的数据,比如显示器、音响。
● 存储器
计算机中将一个二进制数字称为一个比特位,英文名bit,简写为小写b。
将8个比特位组成一组使用,称为一个字节,英文名Byte,简写为大写B。
1024B = 1KB
1024KB = 1MB
1024MB = 1GB
1024GB = 1TB
1024TB = 1PB
1024PB = 1EB
1024EB = 1ZB
1024ZB = 1YB
存储器中用于存储一个字节的部件称为存储单元,每个存储单元内部有8个存储元件,用来存储8个比特位。
存储器按照功能的不同可以分为两类:主存、辅存,对应内存和硬盘。
主存读写速度很快,但是断电后数据无法保存,用于在计算机运行时临时存储处理器需要使用的数据。
辅存读写速度慢一些,但是断电后数据依然可以保存,用于在计算机断电停止运行后保存数据,为了降低成本,一般是将多个存储单元组成一组使用,一组称为一个页,页的容量可以是512B、1KB、2KB等等容量,每个页使用一条地址线与外界相连,读写数据时至少操作一个页。
● 中央处理器
中央处理器是通过纳米级工艺制作的大规模集成电路,内部主要分为4个部分:控制器、运算器、缓存、寄存器。
控制器用来执行CPU对外提供的一些功能,控制器的工作需要由指令决定,指令是一些CPU能够识别的固定格式的数据,一条指令表示一种具体的功能,控制器通过不断的在存储器中读取指令数据从而不间断的工作。
运算器负责进行数学运算、逻辑运算、位移运算。
缓存是一种读写数据速度比内存更快的存储器,内存读写速度虽然很快,但还跟不上CPU处理数据的速度,所以CPU在读取内存数据时,会将其周围的数据一并读取出来,然后存储到缓存中,之后直接从缓存中读取数据。
寄存器是一种比缓存读写速度更快的存储器,一般用于存储程序执行时频繁使用的数据,但寄存器数量有限,程序执行期间并不会将所有的数据都存放在寄存器中。
● 地址空间
CPU使用一个数据指定要读写的存储单元,称为地址,地址能够表示的数值范围称为地址空间。
比如32位的CPU可以使用一个4字节的数据当做地址,总共可以寻址42,9496,7295个存储单元。
地址空间分为两种:内存地址空间、IO地址空间,分别分配给内存和输入输出设备,两个地址空间使用不同的指令进行读写。
IO地址空间的不同范围会分配给不同的设备总线,CPU通过读写不同范围的IO地址空间控制设备总线中的不同硬件设备,硬盘是连接在IO地址空间的,CPU将其当做IO设备管理。