程序是怎样跑起来的--二进制基础运算

一、什么是二进制数

 为了更清晰地说明二进制数的机制,首先让我们把00100111这个二进制数值转换成十进制数值来看一下。二进制数的值转换成十进制数的值,只需将二进制数的各数位的值和位权相乘,然后将相乘的结果相加即可。

首先,让我们从位权的含义说起。例如,十进制数39的各个数位的数值,并不只是简单的3和9,这点大家应该都知道。3表示的是3×10=30,9表示的是9×1=9。这里和各个数位的数值相乘的10和1,就是位权。数字的位数不同,位权也不同。第1位(最右边的一位)是10的0次幂(=1),第2位是10的1次幂(=10),第3位是10的2次幂(=100),依此类推。这部分相信大家都能够理解。那么,我们就继续讲一下二进制数。

位权的思考方式也同样适用于二进制数。即第1位是2的0次幂(=1),第2位是2的1次幂(=2),第3位是2的2次幂(=4), ……,第8位是2的7次幂(=128)。“〇〇的××次幂”表示位权,其中,十进制数的情况下〇〇部分为10,二进制数的情况下则为2。这个称为基数。十进制数是以10为基数的计数方法,二进制数则是以2为基数的计数方法。“〇〇的××次幂”中的××,在任何进制数中都是“数的位数-1”。即第1位是1- 1=0次幂,第2位是2- 1=1次幂,第3位是3- 1=2次幂。

接下来,让我们来解释一下各数位的数值和位权相乘后“相加”这个处理的原因。其实大家所说的数值,表示的就是构成数值的各数位的数值和位权相乘后再相加的结果。例如39这个十进制数,表示的就是30+9,即各数位的数值和位权相乘后再相加的数值。这种思考方式在二进制数中也是通用的。二进制数00100111用十进制数表示的话是39,因为(0×128)+(0×64)+(1×32)+(0×16)+(0×8)+(1×4)+(1×2)+(1×1)=39。

二、移位运算和乘除运算的关系

在了解了二进制数的机制后,接下来我们来看一下运算。和十进制数一样,四则运算同样也可以使用在二进制数中,只要注意逢2进位即可。下面,我们就来重点看一下二进制数所特有的运算。二进制数所特有的运算,也是计算机所特有的运算,因此可以说是了解程序运行原理的关键。首先我们来介绍移位运算。移位运算指的是将二进制数值的各数位进行左右移位(shift=移位)的运算。移位有左移(向高位方向)和右移(向低位方向)两种。在一次运算中,可以进行多个数位的移位操作。代码清单2-1中列出的是把变量a中保存的十进制数值39左移两位后再将运算结果存储到变量b中的C语言程序。<<这个运算符表示左移,右移时使用>>运算符。<<运算符和>>运算符的左侧是被移位的值,右侧表示要移位的位数。那么,这个示例程序运行后,变量b的值是多少?

代码清单2-1 将变量a的值左移两位的C语言程序

a = 39;
b = a << 2;

如果你认为“由于移位运算是针对二进制数值的位操作,十进制数39的移位操作就行不通了”,那么就请重新读一下本章的内容。无论程序中使用的是几进制,计算机内部都会将其转换成二进制数来处理,因此都能进行移位操作。但是,“左移后空出来的低位,要补上什么样的数值呢?空出来的低位要进行补0操作。不过,这一规则只适用于左移运算。至于右移时空出来的高位要进行怎样的操作,我们会在后面说明。此外,移位操作使最高位或最低位溢出的数字,直接丢弃就可以了。接下来让我们继续来看代码清单2-1。十进制数39用8位的二进制表示是00100111,左移两位后是10011100,再转换成十进制数就是156。不过这里没有考虑数值的符号。

实际的程序中,移位运算以及将在本章最后介绍的逻辑运算在使用位单位处理信息的情况下比较常用。虽然这里没有列举具体的程序示例,但对程序员来说,掌握位运算和逻辑运算的机制是一项基本技能,所以一定要掌握。形象地说,移位运算就好比使用二进制表示的图片模式像霓虹灯一样左右流动的样子。不过,移位运算也可以通过数位移动来代替乘法运算和除法运算。例如,将00100111左移两位的结果是10011100,左移两位后数值变成了原来的4倍。用十进制数表示的话,数值从39(00100111)变成了156(10011100),也正好是4倍(39×4=156)。

其实,反复思考几遍后就会发现确实如此。十进制数左移后会变成原来的10倍、100倍、1000倍……同样,二进制数左移后就会变成原来的2倍、4倍、8倍……反之,二进制数右移后则会变成原来的1/2、1/4、1/8……这样一来,大家应该能够理解为什么移位运算能代替乘法运算和除法运算了吧。

三、便于计算机处理的“补数”

刚才之所以没有介绍有关右移的内容,是因为用来填充右移后空出来的高位的数值,有0和1两种形式。要想区分什么时候补0什么时候补1,只要掌握了用二进制数表示负数的方法即可。这部分内容较多,接下来我们就一起来看看表示负数的方法和右移的方法。二进制数中表示负数值时,一般会把最高位作为符号来使用,因此我们把这个最高位称为符号位。符号位是0时表示正数,符号位是1时表示负数。那么-1用8位二进制数来表示的话是什么样的呢?可能很多人会认为“1的二进制数是00000001,因此-1就是10000001”,但这个答案是错的,正确答案是11111111。计算机在做减法运算时,实际上内部是在做加法运算。用加法运算来实现减法运算,是不是很新奇呢?为此,在表示负数时就需要使用“二进制的补数”。补数就是用正数来表示负数,很不可思议吧。

为了获得补数,我们需要将二进制数的各数位的数值全部取反,然后再将结果加1。例如,用8位二进制数表示- 1时,只需求得1,也就是00000001的补数即可。具体来说,就是将各数位的0取反成1, 1取反成0,然后再将取反的结果加1,最后就转化成了11111111。

补数的思考方式,虽然直观上不易理解,但逻辑上却非常严谨。例如1-1,也就是1+(- 1)这一运算,我们都知道答案应该是0。首先,让我们将-1表示成10000001(错误的表示方法)来运算,看看结果如何。00000001+10000001=10000010,很明显结果不是0。如果结果是0,那么所有的数位都应该是0才对。

接下来,让我们把- 1表示成11111111(正确的表示方法)来进行运算。00000001+11111111确实为0(=00000000)。这个运算中出现了最高位溢出的情况,不过,正如之前所介绍的那样,对于溢出的位,计算机会直接忽略掉。在8位的范围内进行计算时,100000000这个9位二进制数就会被认为是00000000这一8位二进制数。

补数求解的变换方法就是“取反+ 1”。为什么使用补数后就能正确地表示负数了呢?为了加深印象,我们来看一上图,与此同时也希望大家能够牢记“将二进制数的值取反后加1的结果,和原来的值相加,结果为0”这一法则。首先,大家可以用1和-1的二进制形式,来彻底地了解补数的相关内容。除了1+ (- 1)之外,2+ (- 2)、39+ (- 39)等同样如此。总之,要想使结果为0,就必须通过补数来实现。当然,结果不为0的运算同样可以通过使用补数来得到正确的结果。不过,有一点需要注意,当运算结果为负数时,计算结果的值也是以补数的形式来表示的。比如3- 5这个运算,用8位二进制数表示3时为00000011,而5=00000101的补数为“取反+ 1”,也就是11111011。因此3- 5其实就是00000011+ 11111011的运算。

00000011 + 11111011的运算结果为11111110,最高位变成了1。这就表示结果是一个负数,这点大家应该都能理解。那么11111110表示的负数是多少大家知道吗?这时我们可以利用负负得正这个性质。假若11111110是负△△,那么11111110的补数就是正△△。通过求解补数的补数,就可知该值的绝对值。11111110的补数,取反加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类型则是32768以上的值。仔细思考一下补数的机制,大家就会明白像- 32768~32767这样负数比正数多一个的原因了。最高位是0的正数,有0~32767共32768个,这其中也包含0。最高位是1的负数,有- 1~- 32768共32768个,这其中不包含0。也就是说,0包含在正数范围内,所以负数就要比正数多1个。虽然0不是正数,但考虑到符号位,就将其划分到了正数中。

四、逻辑右移和算术右移的区别

在了解了补数后,让我们返回到右移这个话题。前文已经介绍过,右移有移位后在最高位补0和补1两种情况。当二进制数的值表示图形模式而非数值时,移位后需要在最高位补0。类似于霓虹灯往右滚动的效果。这就称为逻辑右移。

将二进制数作为带符号的数值进行运算时,移位后要在最高位填充移位前符号位的值(0或1)。这就称为算术右移。如果数值是用补数表示的负数值,那么右移后在空出来的最高位补1,就可以正确地实现1/2、1/4、1/8等的数值运算。如果是正数,只需在最高位补0即可。现在我们来看一个右移的例子。将- 4(=11111100)右移两位。这时,逻辑右移的情况下结果就会变成00111111,也就是十进制数63,显然不是- 4的1/4。而算术右移的情况下,结果就会变成11111111,用补数表示就是- 1,即- 4的1/4。

只有在右移时才必须区分逻辑位移和算术位移。左移时,无论是图形模式(逻辑左移)还是相乘运算(算术左移),都只需在空出来的低位补0即可。下面顺便介绍一下符号扩充。以8位二进制数为例,符号扩充就是指在保持值不变的前提下将其转换成16位和32位的二进制数。将01111111这个正的8位二进制数转换成16位二进制数时,很容易就能得出0000000001111111这个正确结果,但是像11111111这样用补数来表示的数值,该如何处理比较好呢?实际上处理方法非常简单,将其表示成1111111111111111就可以了。也就是说,不管是正数还是用补数表示的负数,都只需用符号位的值(0或者1)填充高位即可。这就是符号扩充的方法。下向我们展示了将符号位扩充到高位的具体流程。

五、掌握逻辑运算的窍门

解释逻辑右移时,提及了“逻辑”这个术语。大家听到逻辑这个词可能会感觉有些难,但实际上它很简单。在运算中,与逻辑相对的术语是算术。我们不妨这样考虑,将二进制数表示的信息作为四则运算的数值来处理就是算术。而像图形模式那样,将数值处理为单纯的0和1的罗列就是逻辑。计算机能处理的运算,大体可分为算术运算和逻辑运算。算术运算是指加减乘除四则运算。逻辑运算是指对二进制数各数字位的0和1分别进行处理的运算,包括逻辑非(NOT运算)、逻辑与(AND运算)、逻辑或(OR运算)和逻辑异或(XOR运算)四种。

逻辑非指的是0变成1、1变成0的取反操作。逻辑与指的是“两个都是1”时,运算结果为1,其他情况下运算结果都为0的运算。逻辑或指的是“至少有一方是1”时,运算结果为1,其他情况下运算结果都是0的运算。逻辑异或指的是排斥相同数值的运算。“两个数值不同”,也就是说,当“其中一方是1,另一方是0”时运算结果是1,其他情况下结果都是0。不管是几位的二进制数,在进行逻辑运算时,都是对相对应的各数位分别进行运算。表2-1~表2-4总结了各逻辑运算的结果。这些表称为真值表。如果将二进制数的0作为假(false)、1作为真(true)来考虑,逻辑运算也可以被认为是真假的运算。真和真的AND运算结果为真,实际上也确实如此。因为如果两方面都是真,答案就是真。

 

 

 

 

掌握逻辑运算的窍门,就是要摒弃用二进制数表示数值这一想法。大家不要把二进制数表示的值当作是数值,而应该把它看作是图形或者开关上的ON/OFF(1是ON,0是OFF)。逻辑运算的运算对象不是数值,因此不会出现进位的情况。看起来好像有些麻烦,总之就是不要将它作为数值来考虑。另外,还有一点非常重要,就是要对各种逻辑运算分别能实现什么有一个整体印象。形成这样的印象后,即使不看真值表也能判断出运算的结果。下图表示的是对NIKKEI的头两个字母NI这一图形模式进行各种逻辑运算后的结果。假设白色部分表示1,黑色部分表示0。通过下图,我们就会对逻辑运算有一个具体的把握,即“逻辑非是所有位的取反操作”“逻辑与是将一部分变为0(复位到0)的操作”“逻辑或是将一部分变为1(复位到1)的操作”“逻辑异或是将一部分进行取反(相同取0,不同取1)的操作”。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章