【技术白皮书】外功心法 | 第二部分 | 计算机运行原理(数据是用二进制数表示的)
数据是用二进制数表示的
- 前提问题介绍
- 用二进制数表示计算机信息的原因
- IC的一个引脚表示二进制数的1位
- 采用字节处理逻辑
- 程序中的二进制数据
- 什么是二进制数
- 二进制数转换成十进制数的方法
- 位权
- 基数计算方法
- 计算案例
- 移位运算和乘除运算的关系
- 移位运算
- 左移和右移
- 数据转换案例
- 10进制移位计算
- 便于计算机处理的“补码”
- 补码的计算公式
- 最高位表示符号
- 补码计算
- 补数逻辑的校验
- 补码计算案例
- 有符号数和无符号数
- 正数和负数之间的对比
- 逻辑右移和算术右移的区别
- 逻辑右移
- 算数右移
- 案例说明
- 逻辑右移操作
- 算数右移操作
- 符号扩充
- 总结说明
- 数学运算
- 逻辑运算
前提问题介绍
要想对程序的运行机制有一个基本的理解,首先需要了解信息(数据)在计算机内部是如何表现的,以及它们是如何进行运算的。在使用C、Java等高级语言编写的程序中,数值、字符串和图像等信息在计算机内部都是以二进制形式表示的。换句话说,只要掌握了用二进制表示信息的方法及其运算机制,就能更好地理解程序的运行机制。那么,为什么计算机要使用二进制来处理信息呢?接下来,我们将探讨这个问题的原因。
用二进制数表示计算机信息的原因
大家都知道,计算机内部由各种电子组件构成,其中包括CPU(微处理器)和内存,它们都是集成电路(IC)的一种。IC有多种形状,有的像黑色的蜈蚣,侧面有数十到数百个引脚;
有的则类似于插花用的针盘,内部引脚并排排列。
集成电路(Integrated Circuit,简称IC)分为模拟IC和数字IC。我们本篇文章主要讨论数字IC,关于内存IC的内容将在后面的文章进行介绍说明。
每个IC的引脚仅能表示两种状态:直流电压为0V或5V。这意味着,IC的每个引脚只能代表这两种状态。
大多数IC的电源电压为+5V,但为了降低功耗,有些IC会使用低于+5V的电压。当IC的电源电压为+5V时,引脚状态不仅包括0V和+5V,还可能存在高阻抗(high impedance)状态,即不接收电流信号。
集成电路(IC)的这一特性使得计算机只能以二进制数来处理信息。由于每个引脚(1位)只能表示两种状态,二进制的计数方式因此形成了0、1、10、11、100等形式。虽然二进制数并非专门为IC设计,但它与IC的特性高度契合。计算机处理信息的最小单位是“位”,对应于二进制中的一位。位的英文“bit”是“binary digit”的缩写。
IC的一个引脚表示二进制数的1位
二进制数的位数通常为8位、16位、32位等,均为8的倍数。这是因为计算机处理信息的基本单位是8位二进制数,称为一个字节(Byte)。
字节是信息的基本计量单位,其中位是最小单位,而字节则是基本单位。内存和磁盘在存储和读写数据时使用字节作为单位,单独使用位单位则无法进行有效的数据操作。因此,字节被视为信息的基本单位。
采用字节处理逻辑
在使用字节单位处理数据时,如果数字的位数小于所需的字节数(即二进制数的位数),则高位会用0进行填充。例如,6位的二进制数100111在8位(1字节)表示时为00100111,而在16位(2字节)表示时为0000000000100111。
以奔腾等32位微处理器为例,它们具有32个引脚用于信息的输入和输出,这意味着奔腾可以一次处理32位(即4字节)的二进制数信息。
程序中的二进制数据
在程序中,即使使用十进制数或文本来表示信息,经过编译后,这些内容都会转换为二进制数。因此,程序在运行时,计算机内部处理的信息实际上也是以二进制形式表示的。
对于以二进制形式表示的信息,计算机并不区分其内容是数值、文字还是图像等类型。相反,计算机根据程序员编写的指令来处理和运算这些信息。
什么是二进制数
什么是二进制数?为了更好地理解二进制数的运作机制,我们可以将二进制数 00100111 转换为十进制数。转换过程非常简单:只需将每个二进制位的值与其对应的位权相乘,然后将这些乘积相加即可得到十进制结果。
二进制数转换成十进制数的方法
首先,让我们先了解位权的概念。以十进制数39为例,其各个数位的数值并不仅仅是数字3和9。我们知道,3实际上表示的是3×10=30,而9则是9×1=9。在这里,与各个数位相乘的10和1被称为位权。位数的不同对应着不同的位权。
最右侧的数位是10的0次幂(即1),第二位是10的1次幂(即10),第三位是10的2次幂(即100),依此类推。相信大家对此有了清晰的理解。接下来,我们将继续探索二进制数的相关内容。
00100111(二进制数) =>(0×2^7)+(0×2^6)+(1×2^5)+(0×2^4)+(0×2^3)+(1×2^2)+(1×2^1)+(1×2^0) =>(0×128)+(0×64)+(1×32)+(0×16)+(0×8)+(1×4)+(1×2)+(1×1) =>0+0+32+0+0+4+2+1=> 39(十进制数)
位权
位权的概念同样适用于二进制数。在二进制中,第1位对应的是2的0次幂(即1),第2位是2的1次幂(即2),第3位是2的2次幂(即4),以此类推,第8位则对应于2的7次幂(即128)。这里的“基数”表示为“基数的次幂”,在十进制中,基数是10,而在二进制中,基数则是2。
基数计算方法
十进制数采用的是以10为基数的计数系统,而二进制数则以2为基数进行计数。在“基数的次幂”表达式中,次幂的值始终为“位数减去1”。具体来说,第1位对应0次幂(1-1=0),第2位对应1次幂(2-1=1),第3位对应2次幂(3-1=2),以此类推。
十进制数是以10为基数的计数方法,二进制数则是以2为基数的计数方法。
为什么各数位的数值与其位权相乘后需要进行“相加”。实际上,数字所表示的值正是各数位的数值与其对应位权相乘后所得结果的总和。
例如,十进制数39可以分解为30和9,这实际上是将各数位的数值与位权相乘后相加的结果。
计算案例
这种思维方式同样适用于二进制数。以二进制数00100111为例,其对应的十进制值为39。这是因为计算过程为:(0×128) + (0×64) + (1×32) + (0×16) + (0×8) + (1×4) + (1×2) + (1×1),最终得出的结果是39。
移位运算和乘除运算的关系
在熟悉了二进制数的基本机制后,我们接下来探讨其运算方式。与十进制数相似,二进制数同样支持加、减、乘、除四则运算,关键在于遵循“逢二进一”的原则。然而,二进制数还拥有一些独特的运算方式,这些运算方式不仅是计算机内部处理数据的基础,也是理解程序运行原理的关键所在。
移位运算
首先,让我们介绍一下移位运算。移位运算是指对二进制数值的各个位进行左移或右移的运算。左移(向高位方向)和右移(向低位方向)是两种常见的移位操作。在一次运算中,可以对多个数位进行移位操作。
左移和右移
-
左移运算:将二进制数的各个位向左移动n位,右侧空出的位用0填充。左移n位相当于将数值乘以2的n次方。
- 规则:将操作数的二进制位向左移动指定位数,右侧空缺位补0。左移
n
位等价于原数乘以2^n
。
- 规则:将操作数的二进制位向左移动指定位数,右侧空缺位补0。左移
-
右移运算:将二进制数的各个位向右移动n位,左侧空出的位用原有的最高位填充。右移n位相当于将数值除以2的n次方并取整,但需注意负数右移可能引发意外结果。
- 将操作数的二进制位向右移动指定位数。对于无符号数,左侧补0;对于有符号数,左侧补符号位(正数补0,负数补1),称为“符号位扩展”。
举一个案例,是把变量 a 中保存的十进制数值 39 左移两位后再将运算结果存储到变量 b 中的程序端。
a = 39;
b = a << 2;
<< 这个运算符表示左移,右移时使用 >> 运算符。<< 运算符和 >> 运算符的左侧是被移位的值,右侧表示要移位的位数。那么,这个示例程序运行后,变量 b的值是多少。
如果你认为"由于移位运算是针对二进制数值的位操作,十进制数39的移位操作就行不通了"?实际上,无论程序中使用的是几进制,计算机内部都会将其转换成二进制数来进行处理,因此可以进行移位操作。当对十进制数39进行移位操作时,计算机将其转换为二进制数值进行处理。
对于左移操作,空出来的低位需要使用0进行补充。这个规则仅适用于左移运算。至于右移时空出来的高位需要进行怎样的操作,我们将在后面的内容中进行说明。另外,移位操作会使最高位或最低位的数字溢出,直接丢弃即可。
数据转换案例
十进制数 39 用 8 位的二进制表示是 00100111,左移两位后是 10011100,再转换成十进制数就是 156。不过这里没有考虑数值的符号。至于其原因,之后大家就知道了。
溢出风险与性能优化:位运算注意事项、硬件指令映射,空出来的低位补0。
移位运算在某些情况下可以替代乘法和除法运算,实现更高效的计算。举个例子,将二进制数值00100111进行左移两位的结果是10011100。经过左移两位之后,数值变为原来的4倍。如果用十进制数表示,原来的数值是39(00100111),左移两位后变为156(10011100),正好是原来的4倍(39×4=156)。
通过利用移位运算来代替乘法和除法运算,可以在某些场景下提升计算的效率。移位运算通常比乘法和除法运算更为快速,特别是当需要对大量数据进行相同的计算时。
10进制移位计算
可以发现十进制数左移后会变成原来的10倍、100倍、1000倍,而二进制数左移后会变成原来的2倍、4倍、8倍。而二进制数右移后则会变成原来的1/2、1/4、1/8。这一规律解释了为什么移位运算可以代替乘法运算和除法运算。通过不断左移或右移二进制数,我们可以实现尺度的变化,达到了乘法和除法的效果。
便于计算机处理的“补码”
先前之所以未涉及右移操作的详细说明,原因在于右移后所腾出高位的具体填充值,依据情境可分为0和1两种。要准确判断何时采用0填充、何时采用1填充,关键在于深入理解二进制数表示负数的机制。
补码的计算公式
补数求解的核心转换技巧可以概括为“取反后加1”。
这一方法为何能精确无误地表达负数呢?为了加深理解并强化记忆,请大家铭记这一重要法则A:“一个二进制数经过取反并加1后,与原数相加,其和为零”。
例如,我们可以通过1与-1的二进制表示,来全面透彻地理解补数的概念。类似地,这一规律同样适用于2与-2、39与-39等数值对。总而言之,要达到和为0的目的,补数的运用是不可或缺的。
最高位表示符号
二进制数中表示负数值时,一般会把最高位作为符号来使用,因此我们把这个最高位称为符号位。符号位是 0 时表示正数 ,符号位是1 时表示负数。
那么-1 用 8 位二进制数来表示的话是什么样的呢?可能很多人会认为“1 的二进制数是 00000001,因此-1 就是 10000001”,但这个答案是错的,正确答案是 11111111。
补码计算
为了获得补数,我们需要将二进制数的各数位的数值全部取反 ,然后再将结果加 1。例如,用 8 位二进制数表示-1 时,只需求得 1,
也就是 00000001 的补数即可。
计算机在做减法运算时,实际上内部是在做加法运算。用加法运算来实现减法运算,是不是很新奇呢?为此,在表示负数时就需要使用“二进制的补码”,补码就是用正数来表示负数,很不可思议吧。
补数逻辑的校验
让我们采用正确的二进制表示方法,将-1表示为11111111来进行运算。此时,00000001与11111111相加,其结果确实为0,即二进制下的00000000。在这个过程中,最高位发生了溢出,但正如我们先前所述,计算机在处理时会自动忽略溢出的位。在8位二进制数的运算范畴内,9位的二进制数100000000会被视作等效于8位的二进制数00000000。
补码计算案例
以8位二进制数为例,当我们计算3减去5时,3的二进制表示为00000011,而5的二进制为00000101。为了求5的补数,我们采用“取反加1”的方法,得到其补数为11111011。因此,3减去5的运算可以转化为00000011加上11111011。执行加法运算后,结果是11111110,其中最高位变为1,这暗示了结果是一个负数,这一点不难理解。
那么,11111110究竟表示哪个负数呢?此时,我们可以利用“负负得正”的性质。假设11111110表示的负数为负X,那么其补数即为正X。为了找到这个正数,我们再次求补数,即对该补数进行取反加1的操作,得到的结果是00000010,这对应于十进制的2。因此,11111110表示的负数是-2,从而我们得出了3减去5的正确结果。
有符号数和无符号数
在编程语言的整数数据类型中,存在能够处理负数的类型与无法处理负数的类型之分。以C语言为例,其数据类型体系内,unsigned short类型不具备处理负数的能力,而short类型则能够容纳负数。尽管这两种类型均占用2字节(即16位)存储空间,并能各自表示2的16次方,即65536种不同的数值,它们在具体数值范围上却有所差异。short类型的数值范围覆盖-32768至32767,而unsigned short类型的范围则是0至65535。
short类型与unsigned short类型之间的另一关键差异体现在数值表示方式上。short类型采用补码形式来表示最高位为1的数值,意味着这些值被解释为负数;相反,unsigned short类型则直接将其最高位视为数值的一部分,从而能够表示超过32767的值,直至65535。
正数和负数之间的对比
深入理解补码机制,我们便能揭示为何在short类型的数值范围中,负数会比正数多一个。对于最高位为0的正数,其范围从0至32767,共计32768个数(包括0)。而对于最高位为1的负数,其范围从-1至-32768,同样包含32768个数,但此处不包含0。因此,由于0被归类于正数范畴(尽管它本身非正非负),导致了负数数量相比正数多一个的现象。
逻辑右移和算术右移的区别
逻辑右移
右移操作依据具体情境,可分为两大类:一类是在数值最高位填充0,另一类则是填充1。特别地,当二进制数的用途在于表征图形模式,而非执行数值运算时,执行右移操作后,我们倾向于在最高位补0。
这种处理方式形象地模拟了霓虹灯广告牌上文字自右向左滚动的视觉效果,故而得名为“逻辑右移”。
算数右移
在处理二进制数并将其视为带符号数值进行运算时,执行移位操作后,我们遵循的规则是在最高位填充与原始数值符号位相同的值(即0或1),这一过程被称为“算术右移”。
若该数值是以补码形式表示的负数,那么在执行右移操作后,空出的最高位将被填充为1,这一做法确保了能准确地进行如1/2、1/4、1/8等比例的数值计算。相反,若处理的是正数,则只需在最高位简单地填充0即可。
案例说明
接下来,我们通过一个实例来具体探讨右移操作。假设我们要对-4(其二进制表示为11111100,这里采用的是8位二进制补码形式)进行两位的右移。
逻辑右移操作
在逻辑右移的情形下,移位后的结果变为00111111,转换成十进制后得到63。显然,这个结果并不是-4的四分之一(-1),因为逻辑右移不考虑符号位,只是简单地在高位补0。
算数右移操作
在算术右移的情形下,我们保留了原始数值的符号位。因此,移位后的结果变为11111111,在补码表示下,这对应于-1,恰好是-4的四分之一(-4除以4等于-1)。算术右移通过在高位填充与符号位相同的值(对于负数即填充1),确保了数值在比例缩放时保持正确的符号和相对大小。
符号扩充
符号扩充就是指在保持值不变的前提下将其转换成 16 位和 32 位的二进制数。
当我们将一个正的8位二进制数,如01111111,扩展为16位二进制数时,直接在其前面补零即可轻松得到正确结果0000000001111111。然而,面对以补码形式表示的负数,如11111111,处理方式同样直观且高效:我们只需在其高位填充与符号位相同的值,即1,从而得到1111111111111111。
简而言之,无论处理的是正数还是以补码形式表示的负数,符号扩充的原则都是一致的——使用符号位的值(0代表正数,1代表负数)来填充新增的高位。这一过程即为符号扩展技术。
总结说明
计算机所能执行的运算大致可划分为两大类:算术运算与逻辑运算。算术运算主要涉及基本的数学操作,包括加法、减法、乘法和除法,这些是日常计算中最为常见的四则运算。
数学运算
在探讨逻辑右移的概念时,我们引入了“逻辑”这一术语,或许会让部分听众初感复杂,但实则其内涵颇为直观。为了更好地理解,我们可以将“逻辑”与“算术”这两个相对的概念进行对比说明。当我们把二进制数视作进行加减乘除等四则运算的对象时,这便是算术运算的范畴。
相对而言,逻辑运算则更侧重于将数值视为一种由0和1构成的图形模式,关注的是这些二进制位的纯粹排列组合,而非其数值意义。简而言之,算术处理的是数值的运算关系,而逻辑则专注于二进制位的逻辑结构。
逻辑运算
逻辑运算则专注于二进制数的每一位,即0和1,进行特定的处理。这类运算涵盖了四种基本操作:逻辑非(NOT运算),用于反转二进制位的值;逻辑与(AND运算),当且仅当两个相应的二进制位都为1时,结果位才为1;逻辑或(OR运算),只要有一个相应的二进制位为1,结果位就为1;以及逻辑异或(XOR运算),当两个相应的二进制位不同时,结果位为1。