01.2.1 数制与编码
01.2.1.1 进位计数制
好的,那么从这个小节开始,我们会正式进入第二章的内容,我们会开始学习数据的表示和运算,通过第一章的学习,我们知道了现代的计算机大致上可以分为这样的五大部件。主存储器可以用来存放数据,而运算器可以对数据进行数学运算或者逻辑运算,所以我们那该死的求知欲告诉我们,接下来我们应该探究的问题是。数据应该怎么在计算机当中表示?也就是如何用二进制来表示另外一个问题,就是运算器是怎么实现数据的?这些算术和逻辑运算的。所以这就是这一章要探讨的两大主题,
那这一小节我们首先要学习什么是进位计数制啊,我们平时使用的是十进制数。计算机能够识别的是二进制数,然后为了方便我们程序员来阅读或者记录我们在计算机专业里边通常还会使用到八进制和16进制数。那这小节包含一些重要的考点,其实就是各种数字之间的转换,二进制八进制,16进制,如何转换成十进制,还有十进制怎么来转换成二进制,八进制,16进制?另外,二八十六这三种进制数之间,又有一些比较特殊的联系,
所以这三种进制之间的转换,我们会放在中间来单独的讲解,最后我们还会介绍两个。啊,简单的概念分别是真值,还有机器数的概念好,那我们首先进入第一个话题,那我们首先来看一下人类最古老,最原始的一种技术方法。数字其实和人类的生产活动是息息相关的,在人类还处于原始部落阶段的时候,这个部落里的人啊,出去摘果子,那么他们会用,比如说画竖线或者画横线的方式。
来记录每一个人摘了多少个果子,那一条竖线就会对应一个苹果,这就是最原始的技术方式,但是慢慢的人们发现,这种技术方式的一个缺点就是。呃,没办法记录苹果数量很多的情况,所以后来就有一些远古的祖先们发现了呃,可以用不同的符号来表示不同的数量。比如我们可以用一个横线来表示,这是五个苹果,而竖线每一条竖线表示的是一个苹果,所以八个苹果可以表示成这样的形式。而17个苹果就可以用三个横线加两条竖线来表示,所以这就是最原始的一种计数方式。
不同的符号会反映不同的权重。横线的权重是五,竖线的权重是一。那基于这种思想所发明的最著名的一种技术方式,就是罗马数字。在罗马数字中I可以表示一,然后v表示5x表示10 LCD m分别表示不同的全值,所以我们现在也会经常使用到罗马数字这样的一竖,表示的是一。然后三数表示的是三四数表示的是四,当然了,在中世纪之后四的表示方式一般是写成一个I,再加一个v,这是表示四,然后如果要表示五的话,
就是一个v。因为v这个符号的权重就是五好,另外比如说13是这么表示的呃,如果要表示一八八八,那么可以用这样的方式表示。总之,这种计数方式的一个基本思想就是不同的符号,会反映不同的权重。我们要把这些符号转换成实际的数值,其实做的都是。加法的操作,一个m表示的是1000,再加上一个d表示的是500,然后再加上三个c,也就是分别加上三个100啊,
然后以此类推。那这种单纯用符号来反映权重的呃技术方法,显然是存在缺点的,如果我们要表示的数字不断增大,那么我们就有可能需要发明更多的符号。比如,想表示一万,那么我们可能会发明一个符号,叫w你想表示十万,那可能还需要发明一个符号,叫y之类的。那随着人类生产活动的发展,人类想要表示的数字会越来越大。那采用罗马数字的这种技术方法,就会产生很多的不方便。
后来,聪明的古印度人发明了阿拉伯数字,是的,你没有看错阿拉伯数字,其实是古印度人发明的,只不过是由阿拉伯人传入欧洲,所以欧洲人以为是阿拉伯人发明的。那阿拉伯数字总共会有十种不同的符号,每一个符号会反映不同的权重,就是会代表不一样的实际数值。另外,他们还发明了我们现在最常使用的十进制技术系统。这种计数方式除了会用符号来反映权重之外,符号它所在的位置也会反映权重,比如说九七五。
这样的一个十进制数,其实它的五这个数权重应该是乘以一个一,然后这个数应该是乘以十的权重。而九这个数要乘以100的权重。每一个数码位,它所处的位置不同,那么它所表示的权重就不一样。那用中国古人的话来讲,就是个十百这么几个位。那我们可以把各个数码位的权重换一种表示方式,个位其实是相当于它的权重是十的零次方。然后十位表示的是十的一次方百位,它的权重应该是十的二次方。好以此类推,总之十进制的这种计数方式,
我们会用每一个数码位,然后乘以这个数码位所对应的一个实际的全值。然后这些全值都是十的,某次方因此我们才把它叫做十进制好,那九七五这个数它只是一个整数,如果还带小数,那其实原理也是类似的。每个数码位,它所在的位置不同,那么它的权值,它的权重也会不一样,就是最高位的这个小数,它的全值应该是十的负一次方。然后第二个小数应该是十的负二次方,以此类推好,
那可以看到这种十进制的计数方式啊,和罗马计数方式的思想是不一样的,引入了乘法的思想。每个符号所表示的数值不一样,权重不一样,另外符号所在的位置也会直接的反映这个符号的权重。那有每一个符号,它所处的这个位置所确定的权值权重,我们把这个东西称为位权。由位置确定的权重,所以叫位权诶,那可以开个脑洞,可以想一下为什么我们人类通常都习惯于使用十进制这种计数方法?其实很大一部分原因是人是拥有十根手指的,比如我们可以用这样的手势表示八这个数,
然后用这样的手势表示十。总共有十根手指,在我小的时候,我会和院子里其他小伙伴打篮球,然后我们会派两个人来负责记分,其中一个人记录的是个位的分。那么,当进球数量达到十个之后,是不是手指就不够用了?此时就需要另一个小伙伴伸出他的一根手指用来表示,这一波人进了十分。然后记录个位分数的,这双小手是不是又可以恢复成零的状态,然后从零开始一二三四五六七这样往上加好,总之由于我们人类只有十根手指,
因此当我们在数数的时候。逢十进一,这样的思想应该是最符合人类习惯的,所以这也是为什么叫十。进制的原因,当我们进行加法的时候,每当加到十,那么就可以往高位进一个数逢十进一。人类有十根手指,所以自然而然的能够想到逢十进一,那这不禁让我想到,也许我们八进制的发明者是海绵宝宝,因为它只有八根手指。如果让他数数的话,肯定就是逢八进一会比较符合他的身体构造好,
那接下来我们把十进制的基础方式推广到r进制。那首先我们要引入一个概念,叫做基数。每一个数码位所用到的不同的符号的个数,就是所谓的基数。比如说十进制,在每个数码位有可能用到的符号会有零一二一直到九,总共有十种符号。那对于一个r进制的数来说,它的奇数就应该是r,也就是有可能会出现r种不一样的符号。计算机世界当中,通常使用到的是二进制,八进制和16进制。对于二进制的数来说,
每一个数码位只有可能出现两种符号,一种是零,另一种是一。对于八进制来说,每一个数码位有可能出现八种符号。分别是零到七,而对于16进制来说,每个数码位有可能出现16种符号,那么零到九这儿总共有十个,我们常用的符号。如果还要再增加,还不够六个怎么办?我们用字母来代替,所以16进制数当中a这样的一个符号,其实它表示的是十这个数。
然后b表示的是11c表示的是12,一直到f表示的是15。那我们上面这个式子其实就给出了啊,各种进制转换成我们熟悉的十进制数的一个转换方式,比如说对于二进制数。一零一点一来说小数点,前边的第一位,它的权重应该是r的零次方,也就是二的零次方。然后这一位的数值是一。然后第二位是零权重是二的,一次方第三位值是一权重是二的,二次方。然后还有小数点后的一位,这一位的权重应该是二的负一次方好,
所以把每个数码位的数值,还有这个数码位的一个位权进行相乘相加就可以得到。这个二进制数,它所对应的十进制的数值。那对于八进制来说也是一样的,小数点前的呃,这个数是五,然后这个数的全值应该是r的零次方,所以这儿乘以八的零次方也就乘以一。小数点后的这一位数值是四,再乘以它的位全八的负一次方,那么就可以得到这个八进制数五点四,它所对应的十进制数值。是五点五。那十进制数,
我们自然不必多说。16进制数,原理也是一样的,小数点前的这一位数值是五位,全是16的零次方。而另外,小数点后的这一位是八。它的位全应该是16的,负一次方,所以转换成与之对应的十进制,同样也是五点五。诶,那来思考这样的一个问题,如果说这个八进制数五点四,这个八进制数加上八进制数啊一点四。
那么,这个加法的运算其实和十进制的运算是一样的呃,小数点后的这两位四+4=8。但是需要注意,由于我们采用的是八进制,所以逢八应该进一,因此对于八进制来说,八进制的零点四+0点四。应该是刚好等于一点零逢八进一小数,这儿保留零,然后往整数位进一。那整数位应该是五+1,再加一就等于七还没有到八,所以五点四+1点四应该是等于七点零。这个是基于八进制的一个加法。
对于16进制也是类似的,我们需要逢16进一,那么比如说这个16进制数五点八,加上一个16进制数加呃零点。九吧。那么,小数部分八+9应该是等于17,那17可以表示为16+1,所以逢16进一,因此应该往这个整数部分。进一,然后小数部分只留下一好,接下来是整数部分的呃加法计算五+0+1应该是等于六。所以五点八这个16进制,加上零点九这个16进制,
应该是等于六点一,我们要遵循逢16进一的一个原则。好,为了保证完整性,我们再来举一个二进制加法的呃,简单的例子,这个二进制数一零一点一呃,加上另外一个二进制数加一个,比如说。一一点。一这样吧。那么,小数部分一+1=2。逢二进一啊,小数部分保留一个零。
然后小数点前的这一位,一+1再加刚才进的这个一那么等于三三应该是等于二+1,所以逢二进一,我们需要再往更高位进一个一。然后这一位会留下一个一。好下一位一加上呃,刚才进的这个一=2那逢二进一这一位保留零往高位,再进一个一。好,接下来最高位的计算一+1=2,那逢二进一,因此这一位保留一个零,再往高位进一个一,因此啊,这两个二进制数。
相加得到的结果就应该是一零零一点零,这就是所谓的二进制逢二进一。那十进制是最符合我们人类习惯的一种技术方式,而二进制是最适合用计算机来存储和处理的一种技术方式。因为我们只需要使用有两个稳定状态的物理器件,就可以表示二进制的零和一了,比如说呃,之前介绍过的高电频,低电频。还有在电容当中,我们可以用电荷的正负性来表示零和一,总之我们可以很方便的使用一些呃物理器件来表示零和一这样的两种状态。另外一个原因,零和一可以刚好对应逻辑值的假和真一般,零表示假一表示真,
那么这就可以很方便的用计算机来实现。逻辑运算。第三个原因,我们可以很方便的使用逻辑门电路来实现算术运算,那跨考的同学可能不知道逻辑门电路是什么?这是一门叫做数字电路的课,里边呃会学习的内容好无所谓,反正逻辑门电路也是用来处理二进制的一个元器件。你只需要知道这个就好了。好,总之这就是为什么现在的计算机世界会使用二进制的一个原因,那除了二进制之外,在计算机世界里,我们还经常会使用到八进制和16进制。这两种技术方式和二进制能够比较好的进行一个对应,
所以如果要给人类呃阅读呃计算机里边存储的一些数据的话。那我们把二进制转换成八进制或者16进制的表示方式会更方便一些。好的,那么刚才我们简单的介绍了,如何把其他的进制数转换成十进制,本质上就是用我们这儿给出的啊,这种算法。不同的数码位在不同的位置有不一样的位权,那么你用每一个数码位的值乘以它在这个位置。的一个位全,然后再相乘相加就可以轻松的转换成十进制好,那由于这是一个很重要的考点,因此我们再来进行一个练习。啊,比如说对于这个二进制数来说,
把它转换成十进制,那可以看到这个二进制的k零位是零,然后k一位是一。k4这个位是1k七,这个位也是一,而小数部分k负一这个是一。k负二也是一,所以我们只需要把数码位为一的呃,这些部分进行一个相乘相加就可以了。那最终算得的结果应该是对应十进制的一百四十六点七五。这个部分跨考的同学需要自己暂停来训练一下,那二进制想要转换成十进制,还可以有一种方法。如果你能熟练的记住每一个二进制位,它的全值是多少?
那么呃,转换是很方便的,二进制数的整数最低位的全值是应该是。二的零次方,也就是一,然后第二位的全值应该是二,接下来第三位的全值应该是四。86。32。64。128。然后小数部分的最高位全值应该是零点五呃,接下来应该是零点二五。再接下来应该是零点一二五。那我们再把这个二进制数和各个位的全值对应上,
所以这个二进制数的实际数值应该是多少呢?就应该是128加。加上16,再加上二,再加上零点五,再加零点二,那么加得的结果也是一四六点七五。这是我个人比较喜欢的一种转换方法,我会先把各个位的位全啊,按照顺序写下来,然后再把二进制数给它对应上去。这样就不需要思考二的七次方到底是多少,二的四次方到底是多少好,所以大家可以记一下这个东西,每一位的位全分别是多少?
那这是二进制转换成十进制,那八进制转换成十进制,我们就不再赘述啊,反正就是用每一个数码位乘以这个位的位权,然后相乘相加。最终得到二五一点五这个八进制所对应的十进制数。好,最后再来一个16进制a一八六点一,那么a这个字母它表示的其实是十。而e应该是14,大家可以自己数一下。那最终转换成十进制,应该是这样的一个值,那对于计算机专业的同学,这些问题应该是很简单很熟悉的,
而跨考的同学可以自己多动手练习一下。也可以自己给自己随便写一些呃数字,然后转换成十进制好,那这是任意进制转到十进制。接下来我们来看二进制如何和八进制,16进制之间进行转换呃,比如说对于这样的一个二进制数。那我们会发现,八进制数,它的奇数r应该是等于八,也就是每个数码位总共有可能出现八种不一样的情况。而二进制数,每一位只有可能出现零或一这样的两种情况,那如果我们把三个二进制位进行一个组合。那这三个二进制位有可能出现的情况,
就应该是二×2×2,也就是等于八,那这就和八进制有可能出现的八种情况能够对应上了。因此,如果我们要把二进制转成八进制的话,其实很简单,我们只需要三个二进制位为一组。然后每一组转换成对应的八进制符号就可以,也就是零到七,那对于上边给出的例子,我们每三个二进制数为一组。那零一零这个二进制数表示的应该是二零零零表示的是零,然后一一一表示的是七。那最高位只剩一个一,由于我们要保证是三位一组,
所以我们会在前边补上两个零,凑足三位。那零零一对应的也是一这样的一个数值,而小数部分也是一样的,三位为一组,如果不够的话啊,最后面这一组我们可能会需要补几个零,把它凑足三位。然后零一一对应八零一零对应的是二。可能还是会有跨考的同学,不知道这个对应关系是怎么来的呃,其实和之前是一样的原理,我们的二进制数零一一,它的最低位的位全应该是一。然后第二位的位群应该是二第三位的位群应该是四。
所以这个二进制数转换成十进制,就应该是二+1=3,因此零一一对应的就是三而一一一。这个数。它所对应的实际数值就应该是四+2再加一,所以应该对应七。好,那这是二进制到八进制的转换,而二进制和16进制的转换其实也是类似的,因为四个二进制数。刚好可以表示出16种不一样的状态,因此每四位二进制为一组,每组转换成对应的16进制符号就可以了。那对于刚才这个数,每四位为一组,
把它转换成16进制所对应的符号,我们重点来看一下一一零零的转换一一零零。那么,根据二进制数,这四位为一组最低位的位全应该是一,其次是二四。四八好,所以一一零零它的实际数值应该是八+4,也就是等于12那么16进制当中怎么表示12这个数呢?a是10b是11c是12,所以可以用c这个字符来表示12,因此一一零零把它转换成对应的16进制符号就是c。好,那对于小数部分也是类似的,只不过我们在最后有可能需要补上几个零,
要凑足四位一组,然后再进行转换。整数部分补零是补在呃头部,然后小数部分补零是补在尾部,这一点需要注意好,接下来看八进制到二进制的转换,其实原理和之前是一样的,只不过是一个逆向的过程。每一个八进制数最终应该可以转换成三位二进制,那么二所对应的二进制应该是零一零。五所对应的二进制应该是一零一。一所对应的二进制应该是零零一,然后小数点之后还有一个五五所对应的应该是一零一。所以这就是这个八进制数转换成二进制数的一个结果。好,
最后还有16进制转二进制也是一样的a,表示的是十这个数值,那么我们用四位的二进制表示十应该是。应该是一零一零,因为最低位是一第二位的位全是二四八那八+2刚好是等于十,因此十用二进制表示就应该是一。一零一零那么e也是类似的,转换成二进制是一一一零好,这些对于跨考的同学来说,需要自己下去练习,这儿就不再赘述。那这儿大家会注意到,我们会用一个括号,然后写一个八的角标,表示这是一个八进制数,
然后同样的方式,一个括号写一个16的角标,表示这是16进制数。那除了这种书写方式之外,我们还会遇到其他的书写方式,比如二进制的表示,我们呃,会看到一串一零一零。然后最后加一个b,因为二进制是binary啊,所以结尾为b表示的是二进制,那16进制还会用。h作为一个结尾,用这样的方式来表示,因为16进制的英文是这样的一个单词,
是以h打头的,而十进制可以。用d来表示。同样也是来自于它的英文。那除此之外,我们还经常会见到0x,然后后面跟一串数字,那这个0x其实也是表示说后面跟的这一串是一个16进制的数。因此,大家在做题的时候,如果题目没有特别的说明,这是什么进制,那就需要观察啊,它的一个书写的方式。特别是以字母结尾的这种书写方式。
好,那目前为止,我们介绍了其他进制转换成十进制的方式,还有就是二进制,八进制,16进制之间的一个转换。接下来,我们还要探讨一个问题,就是十进制如何转变为其他进制?比如七十五点三,这是一个十进制数,那需要把它转换成其他进制,我们需要分为整数部分和小数部分来分别进行处理。首先,我们来处理整数部分,
那通过之前r进制和十进制相互对应的这个公式,我们可以知道。用r进制来表示,这个整数部分的话,那么应该是呃,这样的一个值,这个值刚好就等于75。那如果我们对整数部分的这个表达式统一的除以呃一个奇数r的话,那么就可以得到这样的一个结果。前面这个部分就是除法的呃商,而后边会得到一个余数k0。来解释一下,我们把前边的这个部分记作一个,比如说就叫x吧,所以原本我们放在分子的这个部分,
我们可以把它写成r×x。然后再加上k0×r的零次方,而r的零次方应该是等于一,所以就是直接加上一个k0。好,现在对于r进制的这种计数方法来说,任何一个数码位。的值应该是零到r- 1这样的一个范围,也就是说k0的值肯定也是零到r- 1这样的一个范围。因此,上边这个除式我们用rx呃加k0。除以一个r,我们得到的商应该是x那么一相减得到的余数不就是k0吗?因为k0小于r,所以它肯定是一个余数。
那这就是刚才这个结论的由来,是因为我们任何一个数码位k肯定是小于r的,所以我们这么一除最终得到的余数肯定是k0。那求得了k0是不是就意味着我们已经得知了这个用r进制数表示75的时候?最低的整数位是多少?已经得到了这个整数位的值好,那接下来我们怎么得到k1的值呢?是不是也是一样的原理?我们用之前得到的这个商,再除以一次r,那么这次除法所得到的余数是不是就是k1原理和之前是一样的?好,因此我们基于这样的原理,我们把75转换成与之对应的二进制数。那二进制的奇数r=2,
所以首先75÷2,那么商是37,余数是一。那根据刚才的推论,这个余数一就是k0。接下来,用刚才得到的这个商,再除以r那么得到商是18余数是一,那这个一对应的应该是k1的值。好继续往后是不是一样的?每一次用上一步得到的商再除以这个奇数r用这样的方式一直到。商为零为止。每一步,除法得到的余数就对应了二进制数当中的一个一个数码位,先得到的余数是低位的,
后得到的余数应该是处于高位的。当然了,如果我们做题的时候用这种写法的话,呃会很费纸张,所以我们一般会用短除法的方式。75÷2,那么得到37余一,然后37再除以二呃,得到18余一。好用这种短除法的记录方式会更直观一些,那么最初得到的这个余数就应该是k0,也就是低位。最后得到的这个余数应该是处于高位,那由于每一步我们都会除以奇数r,并且我们会取余数作为最终结果,
因此这种。呃,方法叫做除积取余法。好,所以刚才75这十进制转换成与之对应的二进制,应该是一零零一零一一。从高到低。当然,也可以记录成这样的方式用d表示,十进制用b表示二进制,那如果要把它转换成比如说八进制,是不是也是一样的,只不过我们把这儿的积。r变成八就可以了。同样可以用除积取余法,
把十进制转换成八进制。当然,还有一种方式是,你可以先把它转换成你最熟悉的二进制,然后二进制再转换成八进制,因为二转换成八很容易嘛,之前我们说过。好的,那这是对于整数部分的一个处理,接下来看小数部分怎么处理?也就是我们这儿标蓝的这些部分。那注意观察会发现,小数部分的值零点三,应该是可以表示成这样的一个式子。对这个式子乘以一个奇数r,
那么可以得到呃,这样的一个值,因此我们用小数部分。乘以奇数r,然后得到的这个乘积结果的整数部分就是k负一的一个值。那接下来计算k负二是不是也是一样的?我们再对剩下的这些小数部分乘以奇数r,那么第二次的乘法所得到的整数部分是不是就是k负二的值?好,所以零点三转换成二进制,那么奇数r应该是等于二零点三×2=0点六整数部分是零。然后小数部分是点六,所以这个整数部分对应的就是k负一。接下来,根据刚才的推论,
我们把刚才得到的小数部分再乘以二,那么得到一点二,因此整数部分一就应该是k负二。好以此类推,那我们会发现,用这样的方式进行几轮乘法之后,最后又回到了小数部分为零点六的情况。也就是说,你还可以继续往下沉,然后得到更多的二进制,这其实意味着我们用十进制表示的零点三这个数我们没办法用二进制数来。精确的表示。那我们的转换过程就到此为止,我们的精度是精确到二进制数小数点后的一二三四五五个位。如果这个精度不够,
你继续往后求的话,那么可想而知我们的呃零点三这个数用二进制来表示的话,应该是零点。零一零零一,然后后面又是零一零零一零一零零一无限循环下去。只能不断的趋近零点三,而不能无误差的表示零点三,那同样的,这儿的这种记录方式比较麻烦,需要写比较多的东西,我们可以把它改进一下,零点三×2=0点六。然后这儿我们把它记录下第一个整数零,然后接下来零点六啊,乘以二得一点二记录下整数一。
接下来用小数零点二再乘以二啊,以此类推,那我们最先得到的这个零应该是呃小数部分的高位。越靠后出现的应该是越低位,这个通过我们刚才的推理,不难理解,那由于这种方法,我们每一次会乘以奇数,最终会取整数的部分。所以这种方法叫做沉积取整法好,所以十进制转换成其他进制,我们需要分为整数部分和小数部分来分别进行处理。整数部分,我们要用除积取余法,而小数部分,
我们用乘积取整法。那如果大家能够熟练的记住,或者在稿纸上先提前写出每一个二进制位,它所对应的这个位全是多少?那其实我们也可以用拼凑的方式。把十进制转换成二进制,比如说对于二六零点七五这样的一个数,我们先来看整数部分。二六零应该是等于二五六呃,再加上四那么四应该是这个位置,所以整数部分很显然应该是一零零零一。一零零这是整数部分,而小数部分是零点七五零点七五,刚好是等于零点五+0点二五,所以小数部分应该是点。
一一。好,所以用这种方式可以更快的把呃某一些十进制数转换成二进制,那第二个例子也是一样的,五三三点一二五。首先看整数部分五三三的话,应该是一二五,再加上一个21,那么21的话又可以用16。再拼上五就可以,也就再拼一个四和一。好,所以五三三这个十进制数转换成二进制肯定是一零零零一零一零一。这是整数部分,小数部分点一二五,
那刚好是二的负三次方,所以小数部分应该是零零一。好,所以这样的话,我们就快速的把十进制转换成了二进制,那如果说题目要求你呃,把十进制转换成八进制或者16进制。你在熟悉这种方法之后,完全可以先转换成二进制,然后二进制再转成八进制或者六进制就会很方便。好来试一下,比如把刚才这个转换成八进制,那么三位一组呃一零一对应的应该是呃五。然后零一零对应的应该是二零零零对应的应该是零,然后最后的这个一再补两个零,
它所对应的八进制应该是一。一小数点后的三位呃零零一对应的应该是八进制的一好,所以如果把它转换成八进制的话,一零二五点一。因此,个人认为,呃,如果说它给你的这个十进制数不是特别大的话,那么用这种拼拼凑凑的方法,反而会呃转换的更快一些。我们不一定非要跟着课本上的方法来做好,最后我们介绍两个简单的概念,增值和机器数之前我们已经知道十进制数转换成二进制。那么,二进制可以很方便的保存到计算机里,
但是如果这个十进制数还带正负怎么办呢?那通常的解决方法是,我们会增加一个标志位,符号位,用一个二进制的零或者一来表示正或者负。那在之后的学习中,我们会学到什么原码,反码,补码,以码呃学习这些东西,大家就知道怎么在计算机里来表示带有正负的这样。这种数了,那么这就引出了两个概念,增值和机器数,所谓的增值就是符合我们人类习惯的这种数字。
而机器数是指数字实际存到计算机里的一种形式。我们需要把正负号进行一个数字化,把它变成对应的零或者一。好,那这就是所谓真值和机器数的概念,简单有个了解就可以好,那这个小节的内容比较多,我们介绍了进位计数制。呃,这地方打错了,应该是制度的制,那我们介绍了所谓的r进制数是什么意思?所谓r进制数,就是每个数码位有可能出现r种字符,然后逢r进一,
那把r进制数转换成对应的十进制数的转换方法很简单。我们只需要用每一个数码位乘以这个数码位所在的位置的一个位权,然后相乘相加就可以。那对于程序员来说,我们需要熟练的掌握呃二进制到八进制或者16进制的转换,每三个二进制位对应一个八进制位,每四个二进制位对应一个16进制位。那如果说题目让你把16进制转换成八进制,应该怎么办呢?你可以先把它转成12进制,然后再把二进制转成八进制。经过这样的一个中间步骤就可以呃,需要注意的是,我们需要补位,因为我们转换的过程中是三位一组或者四位一组。
那如果说每一组凑不够呃这么多二进制数的话,我们就需要进行一个补位,对于整数部分,我们通常是在高位进行补位。而对于小数部分,我们通常是在低位进行补位。那这个小节的后半部分,我们又介绍了十进制转换成r进制的一种转换方法,需要分为整数部分和小数部分来分别进行处理。整数部分的处理方法叫做除积取余法,每一步除以奇数得到余数,然后先得到的余数应该是整数部分的低位。那对于小数部分的处理,使用的是乘积取整法先算得的这个数倍,应该是小数的高位。
这一点一定需要注意,那除了课本上介绍的方法之外,如果大家能够熟练的写出各个位的位全,那么其实用拼凑的方法来进行转换。也是很快的,当然这个取决于大家的个人习惯,那么这个小节最后我们还介绍了增值和机器数的概念,这个简单的了解一下即可。另外还有一点需要强调的是,在用乘积取整法把小数部分转换成某进制的时候,有可能会遇到。无法精确表示的情况,比如刚才我们举的零点三这个例子,而对于某些十进制数,比如零点五或者零点。
呃七二五对于这样的数,你只需要用乘积取整法呃乘上几次你就会发现你的小数部分变成了零点零。那对于这样的数,就说明我们可以用二进制来精确的表示,那这一点也是选择题当中的高频考点。对于整数部分来说,我们肯定可以用二进制来精确表示,但是小数部分有可能无法精确表示。好的,那以上就是这个小节的全部内容讲的比较细,也是为了照顾到跨考的同学,那大家还是需要通过课后习题来进行巩固和练习。只有自己动手,才能真正掌握这些方法,那这个小节的结尾和大家分享一个好玩的小东西,
就是你知道中国古代其实就已经有二进制系统了。我们看一些电影或者电视剧会遇到一些什么神算子,对吧?先生,要不要算上一卦?你的印堂发黑,可能有凶兆。那很多算命先生都是呃,基于周易的这个八卦系统来给人算命的,不知道大家有没有听过这样的一句话,叫做太极生两仪。两仪生四象,四象生八卦。那你会发现太极的阴和阳其实可以对应二进制的零和一。也就是这儿所谓的两仪,
可以把对应上零和一,然后这儿的四项还有八卦,其实也是零一零一的组合,只不过八卦你得用三个二进制位来表示。然后四象你得用两个二进制位来表示好,所以这是很有意思的一个东西,和我们的二进制系统有某些异曲同工之妙。好的,那基础不好的同学再好好消化一下吧。
03.2.1.3 定点数的编码表示
好的,这一小节我们要学习的是定点数的表示,那我们首先来认识一下什么叫定点数与定点数相对应的另一个概念,叫做浮点数。所谓定点数,就是指小数点的位置固定不变,而浮点数是小数点的位置是不固定的,会浮动。用大家熟悉的十进制数来类比的话啊,所谓定点数就是我们平时更习惯使用的这种常规的计数方式,我们会显示的标明小数点的位置。而浮点数呢,就是我们用科学计数法表示的呃,这种数。虽然我们把小数点的位置标注在了这个地方,
但是如果在乘上我们给出的这个阶码,那么我们的这小数点其实是会往后移的。那这是我们熟悉的十进制,其实对于二进制也是类似的,二进制的数据表示也分为定点数和浮点数。虽然都是零一零一组成的,但是我们终归需要确定这个小数点的一个位置,那如果我们规定小数点的位置固定不变,这就是定点数。如果我们还需要加上一个类似于科学计数法的这种接码,那么这种数就是浮点数。那王道书里二点二这个部分讲的是定点数,在计算机里边怎么进行加减乘除这些运算?然后二点三这个部分我们会学习浮点数的表示和运算,那这个小节我们先学习定点数,
怎么表示分为无符号数和有符号数?其中有符号树,可以用原码,反码,补码,乙码这样的四种方式来表示,那这一小节是很重要的基础,同时也是考试的重点。我们需要重点关注这几种码之间的相互转换问题好,首先我们先看一下比较简单的无符号数。所谓无符号数,就是指整个机器自长的全部二进制位,都是用来表示数值,并没有正负这样的符号之分。相当于我们使用所有的二进制位来表示数的绝对值,
比如像这个二进制数,它所对应的十进制就应该是156。那具体怎么转换?这个是我们在之前的小节中已经重点强调过的内容,就是用每一位二进制数乘以这一位的位全。然后所有的位进行一个相乘相加就可以,那我们需要重点探讨一个问题,就是无符号数的表示范围是多大?那既然要聊它的表示范围,我们自然需要确定一个前提条件,就是我们到底用多少个比特位来表示一个无符号数。位数越多,显然表示的范围越大。比如,我们使用八位二进制来表示,
一个无符号数,那么八位二进制总共可以有二的八次方种状态。分别是全零到全一那所有的二进制位都为一,这个二进制数所对应的真值应该是多少呢?可以用很多种思路来确定这个二进制数所对应的真值,首先最土的办法,我们是不是可以把每一位呃和位全相乘相加?那八个二进制位就相当于是二的七次方加二的六次方加,一直加到二的零次方。那这是一个简单的等比数列求和问题,得到的结果应该是二的八次方减一。那二的八次方减一,应该是等于二五五,所以八位二进制数,它表示的范围数值范围就应该是零到二五五,
这样的一个范围。那这是用每一位乘以自己的位全相乘相加,这样的方式算出来的一个值,我们再介绍一种思路来算八个一所表示的值。现在我们给这八个一,再加上一个一,那么末位一+1=0,向高位进一一+1又等于零,再向高位进一。好,那所有的这些都是等于零进一,最后我们会往更高位再进一个一那更高的这个位,它的全值应该是二的八次方,因为它是第九个二进制位。所以如果我们在原先这个值的基础上加一=2的八次方,
那么原本的这个值不就是二的八次方减一吗?所以我们也可以用这样的思路来确定八个一,它所表示的一个值。那如果把刚才这种思路再进行一个推广的话,n位的无符号数,它能表示的范围显然就应该是零到二的N次方减一。好,所以这是n位无符号数的一个表示范围。所有的二进制位都是用来表示数值而没有符号位,所以称为无符号数。那一般来说,当我们谈到无符号数的时候,我们通常只会讨论无符号的整数。而没有无符号的小数。这点大家应该是有感受的呃,
比如大家学习C语言的时候,它有一个关键字叫unsigned,这个关键字你只能用来修饰unsigned int。或者long这些整数相关的呃,关键字大家可以去试一下,如果用unsigned去修饰float。就是这种浮点数的话,那它的编译器是会报错的,所以当我们在谈到这种无符号数的时候,通常我们只会探讨整数无符号的整数。而不会探讨小数好,那这是无符号数好,接下来我们来探讨有符号数的定点表示啊,对于有符号数来说,我们既需要考虑到整数如何表示,
也需要考虑到小数。如何表示?那如果要表示整数的话,我们通常会规定小数点是固定的,隐含在最低位的后面。然后最高位的这个二进制位表示的是符号位,通常零表示的是正,然后一表示的是负。另外,如果我们要用定点的方式表示小数的话,我们通常会规定最高位为符号位,小数点隐含在这个符号位的后面。因此,如果我们用这种定点的方式来表示一个,比如说是十九点七五这样的数的话,
那我们需要把整数部分。单独的保存。然后把小数部分单独的保存。那我们可以用原码反码补码,这样的三种方式来表示定点的整数或者小数,另外还可以用乙码来表示定点的整数,这个我们一会儿会展开。在之后的讲解中,如果我们把真值记为x的话,那我们会用这种x+1个中括号,然后右下角啊,标注原法不移这样的方式来表示。x所对应的原码,反码,补码和乙码。
对了,这个地方还有一个概念叫做尾数,就是指我们用来表示整数或者小数的数值部分的这些个二进制位,我们就把它称为尾数。然后最高位我们把它称为符号位。好,接下来我们先来看最简单的源码,那所谓源码呢?就是指我们会用尾数来表示真值的一个绝对值。其实就是之前我们一直在探讨的,大家比较熟悉的那种二进制数表示方法,那我们假设机器字长是八位。啊,也就是说我们有一位用来表示符号,然后剩下的七位用来表示数值,
也就是尾数。好,首先来看一下定点整数用源码怎么来表示每一个定点整数占八个比特位,最高位是符号,然后后续每一个位的全值呃,在这儿已经标出来了。那如果说我们要用源码的方式表示正的19的话,首先符号位正号应该是对应零。然后后续的这七个尾数,我们需要表示成这个真值的绝对值,也就是19,19应该是等于16加。二再加一所以19所对应的二进制就应该是一+2再加16,16应该是二的四次方。好,
所以19的尾数就应该是零零一零零一一,那如果我们要表示的是负19的话,是不是只需要把符号位从零变为一就可以了?好,所以这就是19和负19的一个源码表示。注意我们的定点整数小数点是默认,隐含在了最后面这个位置,所以我们才可以确定这一位的位全是二的零次方。再往上,一位是二的一次方,如果说我们规定小数点隐含在这个位置,那是不是意味着小数点左边这一位的全值是二的零次方?再往上是二的一次二的二次二的三次,而小数点往右的这几位全值就会变成二的负一。二的负二,
二的负三,变成这样的一个情况好,所以我们这儿确定各个位的全值,其实是基于我们小数点的这个隐含位置来进行判断的。好,那这是用源码表示的定点整数很简单,就是我们熟悉的那种表述方式,只不过如果题目规定了啊,我们的这个定点整数,它需要占多少位?那么,我们需要把高位的这些部分补上零好,接下来看定点小数的一个原码表示。那由于我们的小数点是隐含在了符号位的后面,所以小数点右边这位全值应该是二的负一。
往后是二的负二负三啊,一直到二的负七,也就是第一位是零点五,第二位是零点二五。第三位是零点一二五好,后续的就不再展开好,那正的零点七五怎么表示呢?零点七五应该刚好是等于零点五。加上零点二五,所以尾数部分肯定是两个一,然后后续全部是零,那正的零点七五,我们只需要让符号位为零就可以。而负的零点七五,我们把符号位从零改成一,
然后尾数依然是不变的,所以这就是定点小数的一个原码表示。同样的,如果题目告诉我们机器字长的位数,或者指明了源码需要占多少位,那么我们有可能需要在后面补一些零凑足规定的位数。在课本和题目里面,大家也经常会见到这样的一种啊,书写的方式就有的地方可能会在符号为后面打一个小小的逗号啊,但是这个逗号其实是不存在的,是我们脑补上去的。这么写,只不过是为了让大家更好的区分符号位,还有尾数这样的两个部分,另外如果没有明确的指明机器字长是多少?
源码需要占多少位?那么像刚才负19这个数,我们也可以写成一,然后后面跟这样的一串,也就是把刚才我们补上的啊,两个零给去掉。那类似的在书上表示,定点小数的时候也可以写成这样的一种形式,而这个地方是一个小点点,而不是逗号。这个小数点,前面这一位是符号,而不是数值,所以这个数我们不能把它理解为是一点一一,而应该把它理解为是负的。
零点一一。好,这是大家看书做题的时候,有可能会遇到的一种写法好,接下来我们要关注的是源码的一个表示范围。先探讨源码,表示定点整数的一个表示范围。总共有n+1位,其中尾数有n个位,那么n位的尾数可以表示的呃,绝对值的范围应该是零。到二的N次方减一应该是这样的一个范围。这个结论刚才我们在无符号数那个地方探讨过好,那尾数可以表示这样的一个范围,那我们再加上符号位的话。
最小的我们可以表示的整数就应该是负的呃,2N次方减一而正的可以表示的最大整数就应该是二的N次方减一。好,这个应该很好理解,那唯一一点需要注意的是用原码表示定点整数,我们在表示增值零的时候可以有正零和负零这样的两种形式。正零就是符号被为零,然后后续的尾数也全部是零。而负零就是符号位为一,后续的尾数全部是零,因此虽然n+1个二进制位可以表示出二的n+1次方。这么多种呃,不同的状态,理论上我们也可以表示出这么多个数,但实际上你数一下在这个范围内,
其实只包含了二的N次方加一。然后再减一这么多个数,原因就是其中的两个二进制状态对应了同一个真值。好,这一点大家需要注意,那接下来我们再来看,用原码表示定点小数的一个表示的范围。总共n+1个位,其中一个位表示符号,后边的n位表示实际的数值。那么,这n个二进制位所能表示的一个呃绝对值的范围应该是最小是零,就是所有都是零,然后最大的话就是所有都是一。所有的位都是一,
那么它所对应的值就应该是二的负一次方,加上二的负二次方一直加加加到二的负N次方,因为我们总共有n位。那么,这个值加出来就应该是等于一减二的负N次方,因此当我们规定小数点的位置在这个地方的时候啊,这n个尾数所能表示的范围。绝对值的范围就应该是零到一减二的负N次方。好,如果再加上符号位正负的话,那它所能表示的最小值就应该是这样的一个值,最大值就应该是。一减二的负N次方是小数。另外,和之前一样,
我们在表示增值零的时候,同样也可以有正零和负零这样的两种形式。好,那这就是用原码表示定点整数和定点小数的一个方法,还有表示的范围,大家需要特别注意增值零是有两种表示形式的。好,接下来看反码,反码其实很简单,当我们得到源码之后,呃,可以判断,如果说源码的符号位为零,也就是是一个正数,那么反码和原码其实是相同的。
而如果说符号位为一,那么我们只需要在源码的基础上把数值位全部取反就可以,比如像刚才正19这个数,它的源码是这个样子。那由于此时符号位为零,它是一个正数,所以这个数的反码表示和原码表示是一样的,而负19这个数由于符号位为一,所以我们需要把后边的这些数值位。在原码的基础上全部取反,就是零变成一,一变成零,全部取反,这样的话就得到了负19的一个反码。好,
再来看小数,小数也是一样的,正的零点七五。由于符号位为零,所以反码和原码是一样的,保持不变而负的零点七五。由于符号位为一,所以反码的这些数值位需要在原码基础上全部取反零变一一变零。好,所以这就是定点整数和定点小数的反码,表示我们需要先得到原码,然后再转变成反码。那显然,反码和原码之间是有这种一一对应的关系的,所以n+1位的反码整数,
它的表示范围和原码肯定也是一致的。同样的反码也会拥有负零和正零这样的两种形式,原码的正零是全为零,那反码的正零也是全部为零,原码的负零是首位为一。后边全部是零,那根据我们转换的规则,由于符号位为一,所以呃负零所对应的反码就应该是一后边全部变成一。除了反码的整数之外,反码的小数和原码也是一一对应的,所以反码的小数的一个表示范围与原码也是相同的。同样负零和正零会有两种形式好,那这就是所谓的反码,那反码呃,
只是原码转变成补码的这个过程当中的一个中间状态。其实在实际当中,反码并没有太大的用处,计算机硬件不会使用反码直接进行加减乘除,这些运算好的,那这就是反码。接下来我们要探讨如何基于反码来得到补码。对于一个正数来说,正数的补码,它所对应的补码其实就是源码。而对于一个负数来说,负数所对应的补码,我们可以用反码的末尾加一这样的方式迅速的进行转换,那加一的过程中,我们需要考虑到进位的问题。
首先来看用补码来表示定点的整数。对于正19这个数来说,它的原码是这个样子,那么由于它是一个正数,所以这个真值,它所对应的补码形式。其实和原码是一模一样的,好再来看负19,由于这个真值是一个负数,或者说这个符号位它为一,因此我们在求这个。这个真值所对应的补码的时候就需要先把原码的这些尾数全部取反,得到反码。然后再在反码的末尾,这一位再加上一个一。
因此,负19所对应的补码就应该是这个样子呃,比起反码来说,末尾加了一个一。好,再来看。用补码表示的小数正的零点七五,这个小数它的原码是这样,那由于这是一个正数,所以它所对应的补码形式和。和源码是一模一样的,我们只需要把源码照搬下来就可以,然后再来看负的零点七五,由于它是一个负数,所以我们首先需要把源码的尾数部分全部取反。
然后再在反码的末位加上一个一,那需要注意进位的问题,末位的这两个一相加等于零。往高位进一一+1又等于零,再往高位进一,那直到进到这个位置啊,零+1=1。再往前,就不会再有进位了,好,所以这就是负的零点七五,这个真值,它所对应的补码形式。在这一小节中,大家先把注意力放在如何在各种码之间进行转换,
先放在这个地方下一小节,我们再来解释补码这些东西有什么作用。好,接下来我们再来看一下补码是不是也会有正零和负零这样的呃问题?首先看正零,由于符号位为零,所以按照刚才我们提出的这个规则,符号位为零,那么它所对应的补码正零所对应的补码和原码是一样的,都是零。好,再来看负零。由于符号位为一,所以我们把它看为一个负数,因此负零它所对应的补码形式应该是先把它转换成反码。
然后再在反码的末位加一,也就是八个一的末位再加一个一。那这个加法的结果应该是八个零,然后更高位,第九个位会进一个一,但是由于这个地方我们规定机器字长只有八个位。所以我们向更高位进的这个一,其实会被丢弃,而第八位留下的这八个零,是不是和我们刚才推出的正零?这种状态发生了重合,对吧?因此,我们用补码的形式来表示增值零,它只会有一种表示形式就是全零这种方式。
好,那问题来了,源码当中有两个二进制的状态会对应零这个真值,而在补码中只有其中一种状态表示零。那多出来的这种二进制状态怎么办呢?对于这个问题,我们有一个特别的规定。对于定点整数的补码来说,如果符号位为一,然后后续这些都是零的话,那么我们规定这个补码它所对应的数值是。负的二的七次方,这儿有七位表示数值和负二的七次方,这个七是相对应的,那还记不记得对于八位的源码?
和反码来说,八位所能表示的最小数值应该是负的二的七次方减一对吧?而对于补码来说,它可以多表示一个负数最小,可以表示到负的二的七次方。因此,如果机器字长是n+1位,那么补码整数的表示范围应该是这样的,会比原码和阀码多一个负的二的N次方。这一点大家也需要注意,那刚才我们探讨的是用补码表示定点的整数,而如果用补码表示定点小数也有类似的问题。由于正零和负零只有一种表示形式,因此多出来的这个二进制状态,也就是符号位为一,
后续全为零,这种状态我们会规定它的。它所对应的值是负一。原码和反码所能表示的最小值应该是负的一减二的负N次方是这样的一个值,而补码可以多表示一个负一好。好!因此,同样是n+1个比特位,补码会比原码反码的表示范围更多一个数,不管是整数还是小数都这样。好,刚才我们已经知道了,对于给定的一个正数或者负数如何从源码转换到补码。如果我们知道了一个正数的补码,那么想要知道它所对应的源码是很容易的,
因为正数的源码和补码是相同的,但是如果给你一个。复数的补码符号位为一,那么想要推出它所对应的源码应该怎么办呢?其实这个逆向转换的过程和正向转换的过程是一样的,同样是让尾数全部取反,然后末位加一好来看一下。比如说,通过负19的补码来求它的原码,那么我们首先要把这些尾数全部取反零变一一变零。然后在末尾加一,那得到的结果就是一零零一零零一,最后零+1=1。因此,对于一个负数来说,
原码转变成补码,还有补码逆向的转变为原码,方法都是一样的,都是尾数全部取反,然后末尾加一。好,再来看一个小数的例子啊,我们通过负零点七五的补码,求它的原码,那么首先需要把它的尾数全部取反。一零一一一。然后再在末位加一那加了一之后会有一系列的进位,最后得到的结果应该是一点一一。然后后边都是零。所以用这种方式可以得到它所对应的源码,
这种算法是没有问题的。好,那讲到这儿肯定会有很多同学很奇怪,我们搞这么复杂的补码,它到底有什么意义?再次强调,这一小节大家需要把啊注意力放在这些各种码怎么转换这个问题上,因为这是我们很高频的一个。考点好,所以我们先不解释补码的作用,我们继续往后再来看下一种叫做移码,移码很简单,就是在补码的基础上,把补码的符号位取反就可以。但是需要注意,
乙码只能用于表示整数,而不能用于表示小数。好来看一下正19的补码已经得到了,只需要把它的符号被取反就可以得到与之对应的乙码。负19也是类似的,只需要把补码的符号位取法就可以得到与之对应的乙码,那由于乙码是由补码转换过来的,所以乙码和补码的这些数的表示肯定也是一一对应的。也就是说,乙马的正邻和负邻,它同样只有一种表示形式。另外,乙码整数的表示范围和补码也会保持一致。这是因为乙码和补码是一一映射,一一对应的关系好,
来看一下乙码有什么作用,所有的补码的。符号位取反就得到了乙码,那你会发现如果把乙码的所有二进制位看作是一个无符号数,那么当增值增大的时候。乙码所对应的这个无符号数,它也是在逐一递增的,所以乙码的这种特性就保证了,我们可以用计算机硬件来,很方便的实现。两个乙马之间的大小对比。如果要对比两个仪码的大小,那么我们可以让计算机硬件从第一个也就最高位的这个比特依次往后对比。谁先出现一那么就谁更大,而如果说两个都同时出现一继续往后对比就可以,
总之以码的这种特性可以让计算机硬件很方便的判断出哪一个帧值更大。在之后我们学习的浮点数当中,会大量的使用到乙码。好,那这个表给出了八个比特的机器数,它所对应的原码,反码,补码和乙码的值当然了,这儿给出的只是定点整数。因为乙码只能用来表示整数,不能用来表示小数。再次强调原码和反码都有正零和负零这样的两种增值零的状态,而补码和乙码只有一种增值零的状态。所以补码和乙码可以表示更多一个的复数。好,
那到此为止,我们就初步认识了原码,反码,补码,乙码,还有它们之间的一个相互转换。接下来,我们进行一个练习好,首先来看定点整数x等于正的50。用八位的原码反码补码和乙码来表示,那么呃50首先把它转变为原码,那我们先标出每一个二进制位的一个权重。50应该是等于32+16,然后再加上一个二,所以这就是50的一个二进制数表示。
那由于我们总共需要凑足八位,所以我们会在前边再凑一个零,另外最高位作为符号位,由于是一个正数,所以我们标零。那这就是原码,那由于这是一个正数,所以原码反码补码的形式相同,因此反码和补码就不必多说,而乙码是在补码的基础上把符号位取反。所以乙码也可以很方便的得出好再来看第二个例子负的100,首先看一下100用二进制数怎么来表示同样的,我们首先标出各个位的一个全值。然后100应该是等于64+32呃,再加上一个四,
所以这是100的一个二进制数。总共有七位,那由于这是一个负数,所以我们的符号位应该是一,那这就是负100的一个原码。那由于符号被为一,所以它的反码应该是后边的这些尾数全部取反。零变一一变零就可以得到它的一个反码,那补码的话又是在反码的基础上末位加一,那末位加一之后会产生进位。最终得到的补码就是这个样子,那最后再来算一码,一码只需要在补码的基础上把符号位从一变成零就可以。好,那这就是负100的原码,
反码,补码和乙码。好,这是把真值转换成这几种码,接下来我们再来看这几种码转换成真值,我们这儿给出的二进制序列都是一样的,首先看源码呃,它的符号位是一,所以它肯定是一个负数。那它的绝对值是多少呢?一二四八,所以应该是一+4+8=13。因此,这个源码所对应的帧值就是负13,再看反码,
要求反码所对应的帧值,那我们首先得把它恢复成原码。所以这个方码的尾数恢复成原码,应该是这个样子。所以这个驸码所对应的值应该是负的64+32+16再加二啊,结果应该是负的114。好,再看下一个补码,同样的,我们首先需要把补码转换成原码,那之前我们是不是说过补码转换成原码的方式和原码转换成补码是一样的?都是把末尾的这些尾数全部取反,然后再在末位加一,所以这个补码转换为与之对应的源码,就应该是这个样子。
好,那恢复成原码之后就可以知道它的真值是多少,应该是负的六四加三二+16+2,然后这儿还要加一。所以就应该是负的115。好,最后看这个乙码,我们首先需要把乙码转换成补码,也就是把这个符号位变为零。那符号位为零,表示这是一个正数正数的补码和原码是相同的,所以符号位变为零就可以直接得到原码。那这个数值转换过来,就应该是正的13。好,
继续,我们把刚才这一堆的符号位全部从一变为零,然后来求与之对应的真值。由于符号位为零,所以原码补码反码啊,都是同一个意思,那一一零一应该是对应13。再来看最后的乙码,我们首先需要把乙码的符号位由零变为一啊,得到与之对应的补码。然后再把这一串补码转变成与之对应的源码。好,那之前我们算过这串补码,它所对应的值应该是负的115,因此这个乙码最终转换为真值也是负的115。
好,那通过这些练习,相信大家对各种码之间的一个转换应该比较熟悉了,那最后我们再来补充一个呃,快速转换的技巧。如果告诉你一个真值x的补码,让你求负x的补码,应该怎么求呢?一个比较常规的方法是你可以先从补码恢复成。它的源码,然后再通过源码来求负的x的补码。但这种方法比较慢啊,一个更快的方法是我们可以直接把符号位和数值位全部取反,然后再在末位加一。比如现在我们已经知道了13的补码呃是多少,
那按照刚才说到的这个方法,我们把符号位和所有的这些尾数去掉。全部取反,然后再在末尾加一。这样我们就直接得到了负13的补码形式。再比如,我们已经知道负115的补码,我们现在要求正的115补码是多少,那我们可以把所有的这些位。全部取反。然后再在末位加一,那大家可以自己验证一下,这就是115的一个补码,其实也就是它的源码。好,
那这是一个加快大家解题的小技巧,好的,那这一小节我们学习的内容比较多,我们学习了如何把真值用原码,反码,补码和乙码的方式来表示。但是需要注意,乙码只能用于表示整数,而不能用于表示定点小数。那无论是用哪种码,只要我们表示的是定点整数,我们都默认小数点是隐含在最低位的后面。而对于定点小数来说,无论我们用哪种码,我们都默认小数点是隐含在符号位的后面,
那各种码之间的转换既是我们的考试重点,也是我们接下来学习的一个基础。那这张图已经给大家做好了总结,大家可以暂停再来回顾一下,另外我们还需要注意到一个问题,就是这些各种码质,它们对真值零的表现形式。原码和房码有正零和负零这样的两种状态,而补码和乙码表示真值零只能有一种表示形态,也正是由于这个原因,所以原码房码还有补码乙码,它们的一个整数的表示范围。会有所不同,对于补码来说,小数的表示范围也会不一样,
可以多表示一个负一,那由于乙码只能用于表示整数,所以我们不探讨它的小数表示范围。对于乙马来说,所有二进制位为零的时候,它所对应的帧值是最小的,所有二进制位全为一的时候,所对应的帧值是最大的。那基于这样的特性,两个乙码之间可以很方便的用硬件来进行大小的对比。好的,那以上就是这小节的全部内容。
04.2.1.3(拓展)各种码的作用
好的,上个小节中我们学习了定点数的原码,反码,补码和移码,表示那这个小节中我们要着重探讨的是这些各种码,它们有什么缺陷和作用?我们从一个加减运算的例子出发,假设现在有一个字长机器字长为八比特的计算机,有这样的两串二进制数据。那么,我们对这两个二进制数据进行一个加法运算,零+0=0,一+1=0高位进一,一+1,再加一=1。
再往高位进一好用这样的方式,我们可以得到这两个二进制数的一个相加结果。那如果说这两个二进制数,它们表示的原本表示的是一个无符号数的话,上边这个数应该是二+4+8,应该是等于14。而下边这个数,大家可以自己算一下,应该是等于142这样的一个增值14+142,应该是等于156和我们最终得到的这个运算结果是一致的。但是如果这两个二进制数,它们是用源码表示的,有符号数的话,那看一下最高位是符号,这是一个正的数。
下面一个是负的数,而这两个数的尾数都相同啊,都是14,所以上边这个数如果它是原码的话,应该是正14,下边是负14。那正14±14,本来是应该等于零的,然而我们这儿用这种加法运算的规则算出来的结果却不是零。所以这就意味着,当我们对源码进行加减运算的时候,如果说是一个正数,加上一个负数,那么我们需要把这个运算。把它改造成一个正数,
减掉另一个正数,这样的方式也就是14-14,那么我们把呃负14的这个。符号位把它换为零,也就是换为正14,那么两个数一相减得到的结果是零,这样的话就可以得到一个正确的结果。所以从这个例子中,我们可以体会到计算机硬件对源码进行加减运算的时候,必须考虑到符号位。当符号位啊为零或者为一的时候,可能需要硬件自动的对这个加减运算做出一些调整。另外,在算术逻辑单元当中,还必须拥有一个加法器和一个减法器,
那加法器的硬件电路设计起来其实是相对容易的。但是要用硬件实现减法,这个硬件电路设计起来就困难的多,所以硬件既要实现加法功能,也要实现减法功能,就会导致硬件的设计成本增加。同时,复杂度也增加。那这个时候就有一帮很聪明的人,想到了一个方法,就是想我们能不能用加法运算来代替减法运算。好来看一下怎么实现,我们从大家熟悉的十进制的例子出发,现在这儿这是一个时钟,它指向了时点的方向。
假设你现在想把这个时钟调到七点的话,那么我们是不是有两种方式,第一种方式你可以选择逆时针的来调?那由于需要调一格,两格,三格需要调三格,而逆时针我们把它看作是减法,所以这就相当于是十减三等于七这样的一个操作。那另一种方法是,我们可以顺时针的调从十这儿往右调九格,同样也可以达到七这样的一个位置。那如果结合之前的逻辑来解释,逆时针调整,我们把它看作减法,顺时针调整,
我们把它看作加法。所以下边这种方式,我们相当于是十+9,本来是应该等于19的,但是由于我们的时钟盘面,它只有12个数字。所以我们最终时钟的指向其实是要用19来对12进行一个除法,然后得到的余数七就是我们最终这个时钟的指向。在我们做完加法之后,是做了一个取余数的,一个运算,那在数论当中,这种取余的操作我们通常会把它称为模运算。就是19对12取模等于72,就是所谓的模,
其实这个运算就是我们取余数的一个运算。好,所以这个例子想要说明的一点是在我们模12的条件下,其实一个减法操作,我们可以把它转换为一种与之等价的加法操作。我们可以说负三这个数和九这个数在模12的条件下,它们俩是完全等价的。可以相互替换,它们俩等价的意思是说负三对12取余,或者说对12取模,它的余数其实也是等于九。而九对12取模余数同样等于九。那大家可能比较疑惑的是,负数对12取余,这个怎么计算?
在数论中对余数的定义是这样的。对于一个数xx对m取模或者对m取余,这个余数到底等于多少呢?呃,这个余数r应该满足这样的一个式子,我们需要找到一个整数q。q×m再加上一个大于等于零,且小于m就是小于魔术的一个整数。那如果r满足这个式子,那它就是x对m取模的一个余数。像刚才那个负数的例子中负三摩12,它的余数r应该是等于多少?我们代入这个式子来看一下。负三应该是等于负一,也就是这个q乘以魔术12,
然后再加上一个加上一个九。因为这儿的r必须大于等于零,并且小于12,所以我们只能找到这样的一个式子,满足我们这儿对余数的定义,因此负三摩12它的余数应该是等于九。而九模12的余数这个很简单,九应该是等于呃零×12,再加上九所以。所以它的余数就是九。好,因此我们从这个余数的定义就可以知道负三,它模12应该是等于九。九模12也等于九二十一模12也等于九三十三负15,这些数啊,
它们模12都是余九。所以我们可以说在模12的条件下负三九,21,33还有。还有负15这些数,它们就是完全等价的,在进行运算的时候,我们可以用这些数来进行互相的替换。可以看到,所有这些数之间相差刚好就是12负三+12=9,然后九再加12=21,21再加12等于啊33。然后负三,如果减掉一个12的话,是等于15好,
这个应该很好理解,那在模12的这种条件下。对于任意一个整数是不是有可能得到余数r应该是零一二一直到11,总共有12种有可能出现的余数,对吧?所以其实所谓的模运算模12的这个条件是把所有的整数分为了12类,也就是余数分别为零,一直到11这样的12类。而刚才我们举的这个例子当中,所有的这些整数都属于同一类,因为它们模式二的余数都等于九,那么对于这种余数相同的数,我们就把它们看作是同一类,都是等价的。好,
那我们再来结合刚才调时钟的例子看一下。十十点往逆时针调了三格,也就十±3这个加法操作,我们可以用负三的一个等价的数。来进行一个替换,负三和九是等价的,所以十±3等价于十+9,当然在进行这个加法运算之后,我们同样还要进行一个模式,二的这个运算。那19在模12之后,是不是又等于七好?除了九之外,我们还可以用等价的21来替换负三那十+21应该是等于31。大家可以算一下31模12,
同样应该是等于七好,所以在模12的条件下,所有的整数被分为了总共12个种类。那属于同一类的,这些数我们都把它们看作是等效等价的,这些等价的数在进行加减运算的时候可以进行相互的替换,最终得到的效果是相同的。所以回到我们刚开始提出的问题,我们想要用加法来代替减法,其实是可以实现的,比如在模式二的情况下,减三就可以用加九来代替。那由于负三和九这两个数在模式二的条件下,它们等价,另外它们俩的绝对值相加,
刚好等于模数12。所以这两个数我们把它称为互为补数。一对补数,它们的绝对值之和要等于模。显然,这一对补数,它们的绝对值都是要小于我们的模的。好,所以对于一个减法运算,如果我们能够把这个负数找到与之互补的另一个数,那么我们是不是就可以把一个减法操作用加法操作来等价的替换了?那刚才我们说过两个互补的数,它们的绝对值相加,应该是等于模。所以我们只需要用模的值减掉这个负数的绝对值,
就可以得到这个负数,它所对应的补数这个补数是一个正数。这样我们就可以用正数的加法来替代减法了,好回到之前的加减运算,我们这儿用14减掉14,现在我们想要用加法来替代这个减法。那我们只需要找到负14的一个补数就可以在这儿,我们假定计算机的机器字长是八个比特,也就是说这台计算机它只能表示呃八个全零到八个全一。这样的一个范围,也就是零到二的八次方减一,这样的一个范围的数,所以当加法运算超出这个范围的时候,其实计算机只会保留最低的八位。那这个操作就相当于计算机的硬件,
天然的完成了模二的八次方这样的一个运算。因为刚才我们说过模式二是不是相当于把所有的整数?全部映射到了零到11这个范围,那模二的八次方是不是就相当于把所有的整数全部映射到了零到二的八次方减一这样的一个范围?所以计算机硬件的特性,天然的就规定了我们所有的这些加减运算其实都应该把它看作是模二的八次方这样。这样的一个条件下进行的好,所以我们现在已经确定了模应该是二的八次方,那么要确定负14它的一个补数。是不是就用模来减掉这个负14的绝对值,然后就可以得到它的补数?那这儿我们得到负14的补数,应该是这样的一个值,其实就是我们上个小节学习的负14的补码,我们用上个小节学习的方法来。
尝试着转换一下呃,这是负14的源码,那么呃,首先需要把所有的尾数取反。然后末位再加一个一,那最终得到的就应该是四个一零零一零,应该是这样的,和我们这儿算出的结果是相同的。其实用摩减掉这个负数的绝对值,这个才是补码的原生的定义。好,现在我们已经得到了负14再模二的八次方,这个条件下的一个补数,那我们用补数的加法来替代原先的这个减法。呃,
得到的结果应该是这样的,最终会往高位进一个一,但是我们之前说过这台计算机只有八个比特的呃,机器自长,也就是说最高位会被天然的抛弃。硬件天然的帮我们完成了模二的八次方,这样的一个处理好,所以最终留下的这八个位全部为零。刚好转换成十进制,也对应零那负14-14=0,是不是就得到了正确的结果好?所以这就是上个小节学习的补码的一个作用。我们可以用补码让减法操作转变为等价的加法操作。这样的话,我们在alu算是逻辑单元里边,
我们只需要设计加法相关的电路,而不需要设计减法电路。这样的话,硬件成本就可以大大的降低好,那我们结合这个补码的定义来看一下,为什么我们补码的求法是尾数取反,然后末尾加一?对于一个负数,比如说这的负14来说。呃,它的绝对值应该是零零零一一一零,这是负14的一个绝对值。那负14的绝对值,加上它的补数,也就是它的补码应该是要等于模,
也就是等于一个一八个零二的八次方,这样的一个值。好,那它的补码应该是多少呢?如果我们把负14,它的绝对值全部取反,所有的位都取反。那就会变成一一一零零零一,那这两个数它们相加是不是刚好可以得到一个八位全为一的一个数啊?那在这个基础上,如果我们再在末位再加一个一,也就刚才我们加上的这个数,我们再让它加一个一。这样的话,这两个数相加是不是刚好就可以得到一个一八个零对吧?
所以为什么我们通过一个原码来求补码的时候?是符号位不变,符号位不变,然后后续的这些位全部取反,再加一就是这个原因,这个我们结合补码的定义就可以理解。当然了,如果用数学手段很严谨的解释清楚,补码的原理,这个我们还需要很多数论的知识,这就不再展开,大家也不需要深究。这儿给大家讲这些,只是为了让大家知道补码的一个作用,还有它为什么能呈现出这样的作用?
大家能够知道个大概就可以了。好,我们再看一个利用补码把减法变加法的一个例子,88-66,那这儿已经给出了它们的一个二进制表示。现在我们要用加法替代减法,也就是要找到负66的一个补数,它的补码,那它的补码应该是模也就是二的八次方这个值。减掉66的绝对值。那这样我们就得到了它的补码,或者说它的补数,然后这两个数呃e相加,应该是等于这样的一个值,我们把高位的一给舍弃。
后边留下来的这八位呃,它的值应该是这个是二四八十六应该是16+4再加二=10进制的22。这就是88-66的一个正确结果好,总之我们补码的作用就是可以把减法操作转变成等价的加法。这样的话are o当中就不需要再集成减法器,可以让硬件成本下降。另外,当我们在执行加法操作的时候,大家会发现。符号位也会一起参与运算,那想要深究为什么的同学可以去研究一下数论?数论是计算机世界里边很强大的一个数学工具,计算机里边的很多编码方案,还有什么密码学啊之类的,都是基于数论作为一个数学基础。
那有兴趣的同学,自己可以去研究一下好,那这是补码的作用,那这儿我们留下一个坑,刚才我们进行这个补码的加法运算的时候产生了一个进位。啊,这个进位被我们丢弃了,但有的时候这个进位的信息会反映出我们的运算是否产生了溢出?比如大家可以算一下负100,加上负100用补码运算,那显然是会产生溢出的,那溢出应该怎么判断这个问题?我们留在之后的小节再来探讨。好的,那这是补码,
那最后我们再来看一下移码,其实移码的作用上一小节,我们已经说清楚了,由于帧值的这个递增和移码的绝对值的递增是完全一致的。所以如果用仪码来判断两个真值,谁大谁小的话,那用硬件来实现这个逻辑是很方便的,所以仪码的作用就是方便对比大小。好的,那这一小节中我们探讨了源码补码和移码的一个作用,那反码的话就不需要探讨,因为反码只是源码转变成乙码的一个中间状态。源码的作用自然不必多说,它很符合我们对于二进制数的一个理解,但是如果使用源码,
那么要实现减法运算,必须使用一个专门的减法器,这样的硬件来实现。会导致硬件成本的上升,那解决的方法就是用补码的方式把减法转变成加法运算。其实就是要找到一个负数的补数,这样的话,计算机硬件的成本就可以大幅的降低,那最后这个引码的作用就是可以很方便的让我们啊,用硬件电路来比较两个数的大小。好的,那以上就是这一小节的全部内容。
02.2.2 运算方法和运算电路
02.2.2.1_1 加法器
各位同学大家好,在这个视频中,我们要学习怎么设计一个加法器,首先我们会介绍加法器应该具备的基本功能。然后我们会尝试着用门电路实现一个支持一比特加法的一位全加器,接下来基于一位全加器,我们会实现一个可以支持n比特并行相加的。并行加法器,那并行加法器又可以进一步的划分为串行进位和并行进位,这样的两种,最后我们又会基于并行加法器进行拓充。得到加法器的终极形态,也就是带标志位的加法器。好,首先来看一下加法器应该具备的基本功能。
顾名思义,加法器就是用于实现加法运算的。听君一席话,胜读半席话。比如,我们可以用加法器完成七+12=19这样的加法运算。被加数a加数b分别用八比特来表示。加数被加数作为加法器的两个输入,经过加法器的处理之后,得到恩比特的运算结果,也就是输出。那如果我们能用逻辑门电路去实现这样的一个恩比特加法器,那么这件事情简直是太酷了。那我们不妨先分析一下我们手算这两个二进制加法的时候是怎么处理的,我们会从最低位开始。
逐位的相加,并且向更高位产生一个进位,对每一个位的处理都是一样的。当我们从低到高处理完所有位的呃加和运算之后,就得到了最终的一个结果。那为了方便描述,我们不妨把当前正在进行运算的这两个位称为本位。AI表示,来自贝加数a的本位。BI表示,来自加数b的本位,那除了这两个本位的信息之外,我们还需要考虑来自更低位的。敬畏信息。我们把这三个比特相加,
就可以得到本位的和。另外,我们还会向更高位产生一个敬畏的信息。那n个比特的加法运算可以被拆解为n次的一位加法。我们只需要一位一位的往前加就可以。好,那现在我们尝试着用门电路去实现移位的加法。我们要设计这样的一个电路,有三个输入值,分别来自于被加数和加数的本位信息以及。来自更低位的一个敬畏信息。这是电路的输入值,那经过电路的处理之后,我们希望得到这样的两个输出,分别是本位和si。
以及向更高位的进位ci。首先来看怎么用这三个输入值来确定si的值。那经过观察不难发现,当这三个输入值有奇数个一的时候,那本位和就是一。而如果这三个比特有偶数个一,那本位和肯定就是零。那本位和si的逻辑表达式就可以用呃异或运算来实现三个比特进行异或运算,如果其中有奇数个一,那么运算的结果肯定就是一。好,接下来把这个逻辑表达式变成电路,我们可以用两个异或门就可以实现这个逻辑。AI BI ci- 1经过两次抑或得到本位和si。好,
那这样我们解决了怎么用电路求出本位和的问题,接下来还需要确定怎么用电路求出本位向更高位的进位信息。通过观察不难发现,当这三个比特当中只有一个一的时候,那向更高位的呃进位肯定是零。而如果三个比特当中有两个或者两个以上的一的时候,那向更高位的进位肯定就是一。所以这就是进位信息ci的一个运算逻辑。我们可以用这样的方法确定ci的值,首先当两个本位都为一的时候。一定会向更高位产生进位。一。另外,如果AI和BI当中只有一个一并且。来自更低位的进位ci- 1也等于一,
那么在这种情况下也会向更高位进一。那这两个条件当中,满足任何一个条件都会向更高位进一,所以在这个逻辑表达式当中,这儿我们用一个或运算。把它们连起来。那基于这个逻辑表达式,我们可以得到相应的电路,大家可以对比着看一下好,现在我们已经得到了两个输出的值。本位和si向更高位的进位ci,接下来我们把这两个电路合并一下。得到一个统一的电路。在这个电路当中,我们输入AI BI ci减一三个信号。
经过电路的处理,可以输出本位和si,以及向更高位的进位ci。通过这个电路就可以实现一比特的加法运算。那我们不妨把这个电路封装一下,屏蔽内部的这些细节,仅对外暴露出。输入和输出的几条线。那这就是所谓的一位全加器英文缩写叫fa。它可以支持一比特的加法。那之前我们说过n比特的加法可以拆解为n次一比特的加法。好,接下来我们把n个一位全加器给连起来,那通过这个电路就可以实现n比特数的相加。我们只需要把被加数和加数的呃,
这n个比特分别接到对应的这个引脚里边就可以了。最终,这个电路会给我们输出恩比特的运算结果,也就是加和的信息。好,那这个电路图过于复杂,我们不妨把它封装一下,屏蔽内部的一些细节,最终就呈现出这样的一个加法器的形态。好,接下来我们来分析一下这种加法器的实现方式,还有什么不足的地方?那这个地方大家需要知道。虽然电路的呃运算速度很快,我们输入电信号之后可以很快的得到输出的电信号。
但是从我们输入电信号,一直到输出的电信号,到达稳定的状态,中间还是需要一定的时间。也就是说,前一个移位全加器,它产生的这个进位信息有一定的延迟。而只有这个近位信息确定了之后,后一个一位全加器才可以得到正确的输出的结果。因此,在这个电路当中,加法进位信息的产生是一级一级的,往下传递的。也就是说,这些进位信息是串行产生的。
这就意味着,整个加法器的运算速度。取决于这些进位产生和传递的速度。另外,这个加法器的位数越多,显然运算速度也会变慢。那这种串行定位的方式,又可以称为行波定位,就像一个水波一样,不断的往前传。有点类似于多米诺骨牌,前面这一块倒了,后面那一块才会倒一级一级的往前传递。好,那这就是这种加法,
电路的不足之处,进位信息是串行产生的。那刚才这个加法器,它的两个输入端可以并行的输入n个比特。因此,这种加法器属于并行加法器。另外,由于这种加法器,它的进位信息是串行产生的,所以从进位方式的这种角度来看。它属于串行进位的加法器。那很多教材会把这两个定语都用上,把这种加法器称为串行进位的并行加法器。前者描述的是进位方式,后者描述的是可以同时支持恩比特的信息并行输入。
也有的教材会把这种加法器直接简称为串行定位加法器好,这些术语需要注意一下。那刚才我们分析了这种加法器的不足之处。它的主要缺点是敬畏信息,是串行产生的,那如果我们能让这些敬畏信息并行的同时产生。那加法器的速度就可以得到进一步的提升。那基于之前设计的这个串行进位的并行加法器,我们再增加一点点细节。就可以得到一个并行进位的并行加法器。具体来说,就是把这个电路稍微的改造一下,再增加一个cra部件,这个cra部件会同时产生每一个进位的信息。这样就可以实现并行进位的一个效果。
那经过改造的这个加法器,由于它的进位信息都是并行产生的,同时产生的,所以它的运算速度也会有进一步的提升。那这个电路到底是怎么改造的?可以不用深究感兴趣的同学可以看我们的补充视频,我们只需要知道这种并行进位的并行加法器。它的近位信息是并行产生的。知道和前面那种加法器的区别就可以。好那无论是并行进位还是串行进位呃,当我们把这个加法器封装了之后,都可以用这样的一个图示来表示。我们只需要关注这个加法器的逻辑功能就可以,它里面到底是串行还是并行?经纬,
我们不用关心。接下来,我们对这个加法器再进行一点点拓展。我们思考一下加法器运算的结果,一定是正确的吗?不一定对吧?两个恩比特的数相加。结果同样用n比特来表示。那我们知道恩比特的二进制数,它可以表示的范围是有限的,所以如果两个数呃相加超出了恩比特能表示的范围。那最终输出的这个结果肯定就是错误的。那计算机硬件应当发现这种呃出错的情况,对吧?好,
另外我们有时候还需要关心加法运算的这个结果是否为零?比如,当我们想要判断a和b两个数是否相等的时候。计算机硬件在背后的处理逻辑就是用a和b进行相减的操作。或者也可以把它转换成a,加上负b的操作,那a±b就可以用加法器来实现。加和的结果如果等于零。那么是不是就说明a=b这个条件是成立的,如果加和的结果不等于零?那么就说明a不等于b,对吧?所以有时候我们需要关心这个加法器输出的结果是否为零?这对于程序的逻辑判断是非常重要的。好,
我们顺着这个思路继续思考一下,有时候我们写程序还会判断a和b到底谁大。那要判断a是否大于b,是不是可以用a减掉b啊?那a-b就等价于a±b。所以这个减法操作也可以用加法器来实现,对吧?而这个加法运算的结果如果大于零。那么是不是就说明a大于b这个条件是成立的?如果不大于零,那就说明这个条件不成立,对吧?好,因此有的时候我们还需要关心这个加法器的运算结果到底是正的还是?还是负的。
好梳理一下刚才提到的三种情况,第一种情况我们需要关心这个加法运算是否发生了溢出。也就是说,这个加法的运算结果是否超出了恩比特的数,可以表示的范围。第二种情况,有时候我们需要关心这个加法的运算结果是否为零,而第三种情况,我们有可能还需要关心这个加法运算的结果到底为正。还是为负。好,那为了让计算机硬件能识别出刚才提到的这些种情况,我们把这个加法器增加一些标志位的信息,分别叫zf of sf和CF。通过这几个标志位是零还是一就可以判断出我们刚才所关心的这几个问题。
好来看一下,首先第一个标志为of标志为overflow。它是溢出标志。用于判断带符号数的加减运算是否产生了溢出。当of=1的时候,表示发生了溢出of=0的时候,表示没有发生溢出。好,那与之相对应的有另一个标志位叫做CF,如果把英文直译过来,叫做进位借位的标志。我们先不用管它的这个翻译。CF标志位在实际应用当中,通常是用于判断无符号数的加减运算是否产生了溢出。CF=1的时候发生溢出。
CF=0的时候没有发生溢出。显然,我们判断带符号数是否溢出和判断无符号数是否溢出。这个逻辑肯定是不一样的,对吧?比如巴比特的带符号数,巴比特的补码可以表示的范围和巴比特的无符号数可以表示的范围,它肯定不一样。所以,当我们对带符号数和无符号数进行加减运算的时候,判断溢出的逻辑也会不一样。因此,我们需要设置of和CF这样的两个标志位。来分别对应这两种数的加减运算是否发生了溢出。好,
接下来sf这个标志位,它是符号标志。这个标志位用于判断带符号数的加减运算结果到底是正还是负?等于一的时候为负,等于零的时候为正。好,最后一个标志为zf,它是零标志,顾名思义就是用于判断呃,这次加法运算的结果是否为零?当zf=1的时候,表示结果为零。zf=0的时候,表示结果不为零。好,
那这就是带标志位的加法器,我们大致了解了这四个标志位,分别意味着什么?接下来,我们展开介绍一下这几个标志位到底是怎么用电路来生成的。我们把这个加法器展开,看一看它的内部实现细节,首先刚才提到的of这个标志位。它的逻辑表达式是用最高位产生的进位CN和次高位产生的进位CN。cn- 1进行一个异或用这样的方式去生成of标志位。用一个异或门就可以实现,刚才我们说这个标志位反映了带符号数的加减运算是否发生了与溢出?在计算机内部,所有的代符号数都是用补码进行加减运算的。也就是说,
如果我们用两个补码进行加减运算,那么判断这个补码是否发生溢出,只需要看加法器。生成的of标志位就可以。那关于为什么可以用这样的方式去判断补码是否发生了溢出这个问题,我们留到之后再来学习。现在我们只需要知道个大概就可以。好,总之基于之前实现的这个加法,电路我们再增加一个e或门,然后拉两根线出去就可以生成of标志位。好,接下来第二个标志为叫sf。这个标志位的生成更简单。我们说这个标志位反映了带符号数的加减运算的正负性,
也就是说补码的加减运算。运算结果到底为正还是为负?那判断的方法很简单,对吧?我们只需要取恩比特补码的最高位就可以了,所以你看sf这个标志位直接就是从最高位的这个。本位和接了一根线出来。因为最高的这位对应的就是补码的符号,一表示负,零表示正。好,那这是sf这个标志位的生成,接下来zf标志位我们说zf是用于判断加减运算的结果是否为零?那无论是带符号,数补码的这个加减运算,
还是无符号数的加减运算?运算的结果为零,意味着所有的这些比特位肯定都是零。也就是说,当s1到SN。全为零的时候,我们需要让zf这个零标志位等于一,那这个逻辑的实现很简单,只需要用一个或非门就可以。s1到SN先或再取一个非。只有这n个位都为零的时候zf才等于一,其中但凡出现一个一那么zf肯定就等于零。那这是或非的一个效果,那在这个电路图当中可以看到我们把s1。s2一直到SN所有的这些比特位都用一个霍飞门进行了处理。
最终输出了zf这个标志位。好,最后一个标志为CF,这个标志为反映了无符号数的加减运算是否溢出?逻辑表达式很简单,我们只需要把加法器的这个输出的进位信息和输入加法器的这个进位信息进行一个异或。就可以得到CF这个标志位。那如果对应到左边这个电路图的话。c0就是cin。而CN最高位产生的这个进位,对应的就是cout。所以我们从c0这儿接了一根线。输入到异或门。从CN这个地方接了一根线,输入到e或门的另一端,
那用这样的电路就可以生成CF这个标志位。当CF=1的时候,表示无符号数的加减运算发生了溢出,等于零的时候。表示没有溢出。好,那这就是四个标志位的生成,关于of和CF这两个标志位的生成逻辑。我们会在之后讲到补码的加减运算和无符号数的加减运算的时候再来补充。那在这儿,我们能感受到所有的这些标志位都是基于原有的这个加法器的电路进行一定的改造。就可以得到这样的一个呃带标志位的加法器。好的,在这个小节当中,我们学习了怎么设计一个加法器?
我们首先设计了一个可以支持一比特加法的一位全加器。紧接着,我们把n个一位全加器串联起来,得到了一个。串行进位的并行加法器。它可以支持恩比特并行相加,但缺点是各个进位信息是串行产生的,所以导致运算速度比较慢。紧接着,我们基于串行进位的并行加法器进行了一点点的改造,得到了一个可以支持并行进位的并行加法器。那由于进位信息是并行产生的,所以运算的速度得到了提升。那最后,我们又基于串行进位的并行加法器,
进行了一些功能上的拓展,得到了一个带标志位的加法器。这种加法器除了输出n比特的加法运算结果之外,还会输出of sf zf和CF这样的四个标志位。其中of和CF分别用于判断有符号数和无符号数的加减运算是否发生了溢出。sf这个标志位用于判断有符号数的加减运算结果为正还是为负,而zf这个标志位用于判断加减运算的结果。是否为零。注意,我这儿加的定语对于zf这个标志位来说,我没有加任何定语,也就是说zf标志位。既可以判断有符号数的加减运算是否为零,也可以判断无符号数的加减运算是否为零。相比之下of和sf这两个标志位只能判断有符号数的加减运算。
是否发生了溢出以及加减运算的正负性?而CF这个标志位只能用于判断无符号数的加减运算,它的结果是否发生了溢出?这是大家需要注意的细节好,那最后需要跟大家强调这四个标志位,它们的这个电路图是怎么回事?你可以不用关心。但是这个逻辑表达式,你需要知道,同时也需要知道每一个标志位它们到底有什么作用,这些都是考试常考的内容。当然,现在你觉得理解的不够透彻也没有关系,在之后的学习当中,这些标志位还经常会出现。
好的,那以上就是这一小节的全部内容。
2.2.1_0逻辑门电路(数字电路基础补充)
各位同学大家好,在这个视频中,我们会给跨考的同学补充一些和数字电路相关的基础知识,具体来说,我们要通过这个视频了解逻辑门电路有什么作用?那逻辑门电路是用于处理二进制的逻辑运算的,基本的逻辑运算包括与或非这样的三种。而由这三种逻辑运算,可以组合而成,形成更复杂的复合逻辑运算。比如说与非或非,异或和同或。当然,还有其他更复杂的复合逻辑运算,那在这一小节的最后,
我们还会给大家补充一些和逻辑门电路相关的呃,其他的一些大家需要注意的点。那如果本科期间学过数字电路的同学,可以直接跳过这个视频,我们这个视频主要是给跨考的同学零基础的同学量身打造的。好,那首先为了方便大家理解,我们把这节课要介绍的逻辑运算和大家熟悉的算术运算进行一个对比。我们很熟悉的算术运算,包括加减乘除,还有幂次方那加减乘除这四种,我们可以把它称为基本的算术运算。而幂次方,我们可以把它拆解为多次乘法运算,比如说x的三次方,
我们可以把它拆解为x×x再乘以x。也就是说,有两个乘法运算复合而成了x的三次方,这个复合运算,那这就是所谓基本算术运算。和复合算术运算的一个意思,那同样的思想,大家可以类比逻辑运算。呃,算术运算的对象是我们熟悉的实数,而逻辑运算的对象只有二进制的真和假这样的两个逻辑值,也就是。零和一那么对零和一的基本逻辑运算只有三种,分别是与或非。而由这三种基本的逻辑运算进行一系列的组合,
就可以形成复合的逻辑运算,比如说异或运算,就可以用与或非这些基本逻辑运算。组合而成,那具体应该怎么组合?我们一会会讲。那无论是算术,运算还是逻辑运算,都一定要有输入和输出,比如说对于加法,这种算术运算。a+b=c那么a和b就是两个输入的值,而c就是一个计算的结果,也就是输出。那对于逻辑运算来说也一样,
我们也需要有输入和输出,比如说一和零这样的两个逻辑值进行与运算。那参与运算的两个二进制值就是输入,而运算的结果就是输出。我们一会儿会学习各种门电路,这些门电路就是有输入也有输出。用不同的门电路就可以实现不同的逻辑运算。另外,跨考的同学需要知道呃,有一门专门研究逻辑运算的数学学科叫做离散数学。等大家上岸了,可以去学习一下,补充这些基础的数学知识好,那接下来我们进入正题。首先来看基本的逻辑运算与运算。
与运算,英文叫and那我们可以把a和b两个逻辑值中间加一个小点。这个表达式就表示a和b相遇,它们相遇的结果等于y这个值。在逻辑运算当中,我们把一看作逻辑值的,真把零看作逻辑值的假,那a和b进行与运算。必须a和b两个逻辑值同时为真的时候运算的结果y才为真。因此,在这个真值表当中a=1,b=1的时候y才等于一,其他任何一种情况y的值肯定都是零。那这和C语言里边的且是一个意思,只有两个条件都为真的时候,
结果才为真。我们可以用雨门这种逻辑元器件来实现与运算雨门的图形画法,有这样的两种,那左边这种是我们国家的国标画法。那国际上更常用的画法是右边这种。雨门的特点是在输入的这一端,这条线是一条直线AB两个比特输入雨门。输出y这个值那这儿给大家画了一些雨门的示例a和b都为零的时候,输出的y值等于零。a和b其中一个为一,另一个为零的时候输出的值y也为零,只有a和b同时为一的时候。输出的值y才等于一。那最后还需要补充的是与运算的这个表达式,可以省略中间的这个点,
直接把它写成y=AB。好,那这是第一种门电路与门,用于实现与这种基本逻辑运算。接下来,第二个基本逻辑运算或。英文叫or货运算的数学表达式是这样的,这儿不是a+b,应该把它读成a或b。或的逻辑和C语言里边的或条件也是一样的。a和b两个逻辑值进行或运算。那只有a和b同时为零的时候,结果才为0a和b当中,但凡有一个一,那么结果就是一。
那实现货运算的呃门电路,我们把它称为货门,同样的有国标和国际两种画法。我们重点关注右边这种画法,那货门的图形符号,它的特点是在输入的这一端,它是一个弧线。相比之下,雨门它是一个直线,对吧?那值得一提的是,在有的教材当中,或门的画法,这个输出的这一端,可能千奇百怪。
有时候你会见到这样的画法,有时候你也会见到这样的画法,但无论是哪种画法,它都有一个共性,就是输入的这一端,输入的这一端,它肯定是一个弧线。输出的这一端,它的头圆一点,尖一点或者平一点,这无所谓。重点关注输入的这一端就可以。好,这是或门用于实现第二种基本的逻辑运算或。接下来,
第三种逻辑运算非英文叫not非运算的表达式是这样的,在逻辑值a的上面画一个小横线。非这个运算,只需要输入一个逻辑值。如果输入零,那么就输出一,如果输入一,就输出零,总之就是取反。那用于实现非运算的门电路,我们可以把它称为非门,同样的非门有这样的两种画法,左边是国标画法,右边是国际通用的画法。非门最典型的特征就是有这样的一个小圆圈。
在有的教材上,这个小圆圈可能被画在输入端这个位置,这两种画法都是可以的好,这是非门用于实现逻辑非运算。那刚才我们介绍了三种基本的逻辑运算,分别是与或非。接下来,我们会利用这三种基本逻辑运算来构建更复杂的复合逻辑运算。首先是第一种复合逻辑运算叫做与非。英文缩写是anand第一个n指的是not not and,就是与非。逻辑表达式写成这样,也就是说先把a和b两个逻辑值相与,然后上面画一条横线的意思是相与了之后再取非对吧?所以与非这种复合逻辑运算,
可以用一个与门再连上一个非门来实现a和b两个输入值,先用一个与门。相与与得的结果再输入一个非门,取一个反,那这样我们就可以实现与非这个复合逻辑。那在电路图当中,我们每一次都画一个雨门和非门,会显得比较麻烦。为了让电路图的图示变得简单,我们会把中间的这两个门电路。简化把它们揉在一起,可以在一个与门的基础上输出端,这个地方再画一个小圆圈,用这样的方式表示。这是一个雨飞门。
那这就是与非门的画法。与非门的运算逻辑和与门刚好是相反的,因为它就是先把a和b进行与运算,然后再把这个运算的结果取一个反嘛。所以你看这个真值表在与门当中,只有a和b同时为一的时候输出才为一。其他都是零,那在与非门当中刚好相反。a和b同时为一的时候。输出是零,其他的情况输出都是一。好,那这个结合之前的学习并不难理解好,另外与非的这个表达式也可以省略中间的这个点。这和与运算是一样的,
对吧?a和b直接连起来写,然后在上面统一的画一条横线好,这是与非这种复合逻辑运算。第二个符合逻辑运算叫做或非英文缩写,叫NOR第一个n指的是not not all就是或非。到这儿,你应该发现套路了,或非就是a和b两个逻辑值先进行或运算,然后再在或的基础上取一个非。和与非门类似,或非门可以用一个或门加上一个非门来实现a和b两个输入值先进行或运算。货运算的输出接上一个非门,取一个反最终得到输出,那同样的一个货门加一个非门,
这样的画法在电路图当中会显得比较复杂。那么,我们把它简化一下,在或门的输出这一端,加上一个小圆圈,用这样的方式来表示,这是一个或非门。那货飞门的运算逻辑和货门刚好是相反的。在或运算当中a和b只有同时为零的时候,运算的结果才为零。其他任何一种情况a或b结果都为一,那么在或非门当中刚好就是。取一个反对吧,只有a和b同时为零的时候运算的结果才为一,其他的情况呃,
运算结果都为零。反正就是把货运算的结果通通取反好,这是第二个复合的逻辑运算或非接下来来学一个更复杂的复合逻辑运算,叫做异或。英文缩写叫x or第一个x指的是exclusive,这是抑或的英文?异或运算的表达式是这样的,一个圆圈中间加一个加号,这表示异或运算那异或的运算逻辑是什么呢?简单来说,就是当两个输入值相异不一样的时候,输出的值为一。来看这个真值表a输入0b输入一的时候,两个输入值相异,那么输出就等于一。
a=1,b=0,两个输入值相异的时候,输出为一,而两个输入值相同的时候。输出值为零。好,那这就是异或运算。用于实现异或运算的门电路,我们把它称为异或门,我们可以用这样的图形表示,这是一个异或门。现在我们不妨思考一下怎么用与或非这三种基本的逻辑运算来实现异或这种逻辑呢?只有两个输入值相异的时候,输出才为一,
我们试着写这样的一个表达式,先对a取。取非,然后再和b呃相与,那么对于这个逻辑表达式来说,只有a=0 b=1的时候。它们的运算结果才等于一。那再来一个逻辑表达式a与上b飞,那么在这个表达式当中,只有a=1 b=0的时候,它的运算结果才等于。一好,最后我们再把这两个逻辑表达式进行一个或运算或运算的逻辑是左边或者右边,但凡有一个等于一,那么最终运算的结果就等于一。
对吧?所以把这两个部分相互那么整个逻辑表达式的效果就是当AB等于零一或者一零的时候。运算的结果就等于一。那这不就是我们抑或这种复合运算想要达到的效果吗?当a和b相异的时候,输出为一。好,接下来我们不妨把这个逻辑表达式把它变成一个电路。好来看一下这个电路a这个电信号,从这条线过来,那么它会往下走,往下走首先。进了一个非门,对吧?那a取了非之后又输入到了一个雨门,
看一下这个雨门,它是和b。相连的b这个电信号输入这个雨门,那这个雨门输出的值不就是a先取非,然后再和b相遇的这个结果吗?好同样的道理,我们看b这个信号,它除了往下走之外,这个电信号还会往上走一条线,这条线先是经过了非门。也就是对b取非,那b取非之后的这个结果又输入到了一个雨门,再来看这个雨门的另一个输入端,它是连到了a这个电信号。所以上面这儿输入的就是a这个信号,
下面这儿输入的是b取了c的信号,那么。它们俩相与之后,这儿输出的结果不就是a与上BC的一个结果吗?好最后a与上b飞和a飞与上b这两个部分还要经过一个货运算的处理,所以再把它们分别输入到货门的两个输入端。最终输出的这个结果y,它就等效于上面的这个复合的逻辑表达式。而这个复合的逻辑表达式,就是我们要实现的异或的一个结果。那么,在电路当中,如果每一个异或门都画这么复杂,就会让整个图看起来很杂很乱。因此,
我们把整个电路的内部细节隐藏起来,最终简化为这样的一个图形。抑或门的图形特征是,首先这儿有一个或门输入端,这儿是一个弧线,对吧?在或门的输入端前面。还有一个弧线呃,有点像那个双眼皮的那种效果哈,那这就是抑或门的画法。那在之前的这一系列学习当中,我们能体会到在画电路图的时候,为了让整个电路图的逻辑更清晰,更简洁,我们常常会屏蔽某一些电路的内部细节。
把一整个复杂的电路简化为一个更简单,更易懂的呃小的图形,那这就是所谓封装的思想。一个电路部件,我们不需要关心它内部所有的实现细节,我们只需要关心它能够实现什么样的逻辑,什么样的功能。并且对外暴露出输入端和输出端这些呃必要的引脚就可以。那这样的话,对于这个电路部件的使用者来说,他只需要关注这个部件的呃逻辑功能就可以。我给什么样的输入信号,最终会得到什么样的输出信号,只需要关心这个就OK了,具体里边怎么实现的,
它不用关心。那这是不是像极了我们在呃写程序的时候提到的封装的思想也是一样的,对吧?我们经常会调用某一些呃系统给我们封装好的函数。比如说printf。我们只需要知道这个函数,它的功能是什么?而这个函数,它的底层到底是怎么实现的?我不用关心,对吧?我只需要能用就行。所以把一些没有必要的复杂细节封装起来,这种思想既适用于我们的软件的编程,也适用于电路的设计。
好,那这是抑或这种复合逻辑运算,接下来介绍最后一种复合逻辑运算同或x NOR。这是同货的英文缩写,刚才我们学了,抑或抑或的英文缩写叫exclusive or。同或就是中间再加了一个not,也就是说同或和异或唱的是反调,对吧?只需要在异或运算的基础上取一个反就可以,那同或运算的呃符号写成这样。一个圆圈,中间一个点好同或运算,怎么用逻辑门电路去实现呢?只需要在异或的基础上取一个反就可以,
所以异或门的输入端这儿再给它接上一个非门。这一套电路的效果就是同货运算。那我们把这个电路的内部细节给它隐藏起来,封装好,最终简化得到一个图形,就是这个样子。也就是在异或门的输出端这儿加了一个小圆圈,这就是同货,那同货门实现的逻辑和异或门刚好就是相反的,对吧?在异或门当中,当a和b两个输入值相异的时候,输出的值是一。那同货门当中,就应该是a和b两个输入值相同的时候,
输出的值就是一。好,那这是同货门,那由于同货门相当于异,或再取一个非所以在有的教材当中也会把同货门这种门电路。把它翻译为亦或非门。就是对这个英文术语的另一种呃,常见的翻译方式,不过大多数教材还是把它翻译为同货。好,那这是最后一种复合的逻辑,运算好,那在这个表当中给大家总结了,刚才我们学习的与或非。三种基本逻辑运算以及与非或非,
异或和同或这四种复合的逻辑运算。对于跨考的同学来说,你需要重点关注各种门店路,它的一个画法,各个门店路的这个符号特征。我在这儿呃,也给大家做了一个小小的总结,可以帮助大家记住这些图形符号,那另外更重要的是要理解各种逻辑运算的呃,一个运算特征。比如与运算就是当且仅当输入全为一的时候,输出才是一那异或运算就是当两个输入值相异的时候,输出就等于一。好,那这个总结图我也放在了课件里边,
大家可以按需自取。对了,这个地方插播一个题外话,如果我们对n个比特的信息分别进行异或。那么,在这n个比特当中,如果总共有奇数个一,那运算的结果肯定是一。如果有偶数个一,那运算的结果肯定为零,比如在上面这个式子当中,总共有一。二三四四个一,也就是有偶数个一,那么上面这个式子最终运算的结果肯定是零。
而下面这个式子一二三四五总共有五个一,也就是奇数个一,因此这个运算最终的结果肯定是一。那大家可以结合异或运算的逻辑呃,自己验证一下那同或运算和异或运算刚好相反,如果有奇数个一进行同或,那结果肯定是零,如果有偶数个一进行同或。结果肯定是一。好,那这是基于这个特性,异或和同或这两种运算经常被用于实现奇偶校验。而异或运算的特性还会被用于实现二进制的加法,那这些东西我们现在暂时不展开,大家只需要知道多个比特进行异或。
和囤货的时候分别具有什么特性就可以了。好的,那这就是关于逻辑门电路的基础知识,接下来我们根据刚才学习的这些基础知识再来补充一些大家需要注意的地方。首先在课本里边或者在考题当中,大家有可能会见到这样的逻辑门电路的变形画法。我们之前在讲或门与门或非门与非门的时候,我们都默认只有两个输入。所以在我们画或门的时候,它的输入端只有两根线,分别对应a和b两个值。那在有的地方,你可能会遇到这样的画法abcd有四个输入,然后有一个输出。首先,
从它的输入端,它是一个呃弧线,从这个特征你可以知道它是一个货门。那这个或门,它就是把abcd四个逻辑值呃分别相或最终输出y。那显然只有这四个逻辑值同时为零的时候,输出才等于零,但凡其中一个等于一,那么输出肯定就是一。好,这是多输入的或门。那类似的也有可能会遇到多输入的语门。从输入这一端是一条直线,从这个特征可以看出来啊,这是一个与门那abcd四个值分别相与。
最终得到输出值y,那么显然只有四个逻辑值都等于一四个条件都为真的时候它们相与。的结果才有可能是真,那这是多输入的雨门,那类似的还有可能遇到多输入的或非门和多输入的雨非门。右边这两个多输入的门电路,就是在左边这两个门电路输出的基础之上,再取一个反。就是这样的一个逻辑好,那这是有可能遇到的一种变形的画法,有多个输入的门电路。接下来还需要给大家介绍一下逻辑运算的优先级问题呃,我们介绍了三种基本的逻辑运算与或非。那么,非运算的优先级大于与运算大于或运算,
比如说。y=a与b或c与d,那么由于与运算的优先级大于或运算,因此。在计算这个复合的逻辑运算的时候,我们应该先计算a与b和c与d。再把这两个逻辑值最后进行相或这有点类似于我们的乘法和加法,对吧?刚好与运算的这个呃数学表达式。写起来有点像乘法,而或运算的这个数学表达式写出来有点像加法,所以当你在一个逻辑表达式当中看到与运算和或运算同时存在的时候,你按照加法乘法的这种。呃,优先级次序去理解就可以了,
好另外在逻辑表达式当中,我们也有可能遇到这种带括号的情况。我们在刚才这个逻辑表达式的基础上,打上一个小括号,那括号会提升这个运算的优先级,这是不是也和我们熟悉的呃算术表达式是一样的?打了一个括号,意味着你需要先算b或c这个逻辑运算,然后再把结果和a进行相与。那这个相与的结果再和d进行相遇好,因此如果遇到括号,那么括号里边的运算优先级需要提升。这个也和我们熟悉的算术运算是类似的,所以很好理解好,接下来再看一个与或非同时存在的例子,
在这个例子当中,我们需要先对a进行非运算。把a取非之后,再和b相遇,最后再把前边这部分呃得到的值和c相或。这是根据三种逻辑运算的优先级得到的一个运算顺序好,最后来一个例子,我们把刚才的这个例子非运算的这个横线,把它拉的长一点。让它覆盖a和b两个逻辑值,那么运算的顺序就会发生改变。在这种情况下,我们需要先运算a和b相与。与得的结果,再取一个非最终再和c相或也就是说这个式子等价于这样的一个效果。
我们相当于在a和b的外面打上了一个小括号,然后在这个小括号的上面才拉了一个取飞的横线。好,所以虽然非运算的呃逻辑运算优先级高于与和或,但是如果这个非运算它的下面。覆盖了多个逻辑值,那么我们在处理这种表达式的时候,呃,我们可以把它理解为在横线的下面隐含了一个括号。我们是对这一整个括号呃里边的值算完了之后才取了非运算好,那这是三种逻辑运算的优先级问题。接下来再给大家介绍几个呃比较常见的逻辑运算的公式呃,这些公式我们会在离散数学那门课里边有深入的学习。首先是分配律。大家观察一下这个是不是和加法乘法的分配律是类似的。
a与上c或d的一个值,那这个式子等价于a与c或上a与d。类比乘法和加法就可以。好,另外呃,与运算和或运算也有类似于乘法和加法的这种结合律ABC三个值分别相与我们可以呃,先与BC。然后再和a相遇,当然也可以按从左到右的顺序先与AB再与c相遇,这是不是也类似于我们熟悉的乘法的结合律啊?好,那同样的对于或运算,如果没有打括号,我们可以从左到右这么呃,一个一个相互先算a或b。
算得的结果在或上c。但是,由于货运算也有这种结合律,因此我们也可以先算b或c,再和a进行或。好,总之与运算和或运算,无论是在这个优先级方面,还是在分配律,结合律之上呃,都是类似于乘法和加法。大家可以对比着记。那这些公式可以帮助我们去处理与运算和或运算碰到一起的这种式子。我们可以对式子进行一些化简或者等价的变换。那如果我们在表达式当中遇到与运算和非运算碰到一起的情况,
那我们可以用这种繁衍率。就是下面这个公式去处理a和b,先与再飞等价于a飞或上b飞。好,那类似的道理,如果我们遇到或运算和非运算碰到一起的时候,我们可以试着用上面这个式子去把它处理。a和b先后整体在飞,这个效果等价于a飞与上b飞。那这两个式子,我们把它称为繁衍率,在离散数学里边也称为德摩根律。好,那讲到这儿肯定会有不少同学因为这些乱七八糟的呃,离散数学的这些式子应该已经掉了不少头发。
我不知道大家在上学的时候遇到这种很复杂的呃,很难学的,这些东西会不会反问一句,我学这些有什么用呢?对吧?那接下来用一个例子让大家感受一下。呃,这些东西在现实当中的意义,假设现在让你去设计一个呃电路,实现这样的复合逻辑运算。那么,我们是不是可以这么来做a和c相遇?a和d相遇最终再进行或运算?那这儿我们先用两个与门实现a和c的相遇,以及a和d的相遇。
这两个相与的结果,再输入一个或门,实现最终的这个或运算,那回到刚才学习的分配律AC或上AD。是不是等价于a与上c或d的一个值?那既然这两个式子等价,那我们是不是也可以用这样的一个电路?去等价的实现,刚才所要求的这个逻辑功能呢?先把c和d相或。或的结果和a再进行相与,最终得到输出。那实际的效果和我们所要求达到的效果是一致的。那可以看到右边这套电路,相比于左边这套电路来说,
它可以少用一个雨门对吧?只用了一个雨门,一个货门。那我们要知道,制造这些门店路,它是需要成本的,也就是需要钱,所以当你使用这些呃数学的工具去。去简化一个电路的时候,事实上你就是在省钱,就是节约了电路的制造成本。因此,通过这个例子,希望大家能感受到我们刚刚介绍的这些东西呃,并不是为了考试而学习,
它在现实生活当中也有非常重要的作用。我们用离散数学的方式写的这种逻辑表达式,本质上就是对电路的数学化描述。那我们化简这些逻辑表达式就是在简化电路的设计。也就是说,如果让你去设计一个电路,你要做的其实就是推这些数学的逻辑表达式,对吧?只要逻辑表达式推出来了。那这个逻辑表达式肯定可以用与或非这些基本的门店路去实现。再次体会这句话,逻辑表达式只是对电路的一种数学化的描述。所以这些数学的定理到底有什么意义?有什么作用呢?希望大家能有新的体会。
好,那最后给大家拓展一个和考试无关的东西,我们说这些门店路可以实现二进制的运算。那门电路又是由晶体管实现的。这个图是比较早期的晶体管,每个晶体管的大小呃,可能跟指甲盖差不多,还是比较大的。但是现在的芯片制造技术可以把晶体管做的特别特别小,我们经常在新闻里边看到说什么某芯片采用什么十纳米的制程。甚至两纳米的制程对吧?那所谓十纳米的芯片制程意味着这个芯片内部的这种晶体管。山极的宽度也就是十纳米晶体管的山极宽度,基本上就决定了一个晶体管的大小。因此,
芯片的制造工艺,这个芯片的制程呃越精细,那么就意味着在单位面积之内。晶体管的数量就可以越多,晶体管的数量越多,就意味着这些所谓的门店路什么的也会更多。也就是我们可以用更小的一块芯片去实现更复杂的硬件功能,来感受一下十纳米到底是什么概念。我们人类的头发丝直径大概是零点一毫米,也就是十万纳米,也就十纳米的10000倍。那为了实现这种很精密的呃芯片制造工艺,有一个非常关键的制造设备叫做光刻机。目前,全世界呃光刻机技术最先进的是荷兰的a sml,
这个公司有兴趣的同学自己可以去了解一下。好,那这是关于芯片制程这个概念的一个拓展好,那以上就是这小节的全部内容。
2.2.1_2并行进位加法器
好的,上一小节我们利用电路的基本知识设计了一个比较简单的加法器,那这一小节中我们主要会来探讨这些加法器应该怎么进行改进,使它执行加法的速度。更快,那这个小节给大家的学习建议是心平气和,心如止水都行,没事没关系,保持佛性就可以,这个小节的内容会比较绕。啊,不过这一小节并不是我们考试的重点,大家能有个大致的了解就可以了好,那我们首先来看一下上一小节留下的串行进位的并行加法器。那由于我们只是把这些一位全家器进行了简单的串联,
而每一个全家器。对本位和的计算,还有高位的进位的计算,都会依赖于来自低位的这个进位。因此,这种加法器只能实现串行的进位,就像多米诺骨牌一样,只有低位的这些进位。算出来了,那高位的进位信息,还有高位的这些本位和才能得到呃正确的结果。所以这种加法器,它的一个加法执行的速度很大程度上是受到这些进位信息产生的速度的影响。那接下来我们要思考的问题就是,如何让各个进位的信息啊,
产生的更快来看我们每一个进位产生的这个表达式,这个逻辑表达式。ci这个进位依赖于加数和被加数的两个等位的数值,另外还需要基于低位过来的这个进位才可以算出来。只不过这个表达式,它其实具备一种递归的特性,就是ci- 1,我们可以继续把它展开变成这个样子。ci- 1的产生会依赖于di- 1位的加数和被加数,另外也会依赖于更低位进过来的一个进位。好,那我们继续再把ci- 2进行一个展开,又可以得到这样的一个式子,所以如果我们一直一直往下展开的话,终有一天我们可以把它展开到c0。
而c0这个信息是最开始就拥有的一个信息。从这个式子里面可以看出来,我们只需要知道AI一直到a1啊,这些位的信息,然后BI一直到呃b1这些位的信息,然后最后再结合一个c0的信息。我们就可以直接得到,直接算出ci的一个值。所以事实上,每一位向更高位的进位。我们计算这个值所需要的那些数据,在刚开始其实就已经准备好了,因为被加数和加数每一位是多少,我们在刚开始就可以确定。而c0也是刚开始就有的信息,
所以如果我们能够利用刚开始就拥有的这些信息来直接求ci这个进位的信息,那我们就不需要像之前那样等着这些进位一个一个。呃,运算结束才可以算ci的值好,那这就是我们接下来的一个优化思路,那为了让这个表达式看起来更简洁一些,我们把AI和BI相与的。这个部分。把它记作gi,然后把AI和BI异或的这个部分把它记作pi。那这样的话,我们的式子可以得到化简ci的表达式,我们可以写成呃gi+pi。ci- 1啊,当然了,
这儿其实不是加其实是一个或运算,只不过这个地方我们用加还有乘这样的啊方式来描述,大家会更容易理解一些。与和或它们之间的这种关系非常像呃,算术运算里的乘和加。也具有结合律,分配律那些规律。所以接下来的讲解中,我们会把或就念作加把这种雨念作尘。好,那基于这个式子,我们可以知道c1第一位的进位应该是等于g 1+p一×c零。然后c2这一位的信息会根据低位过来的c1,还有a2和b2进行一个运算,那同样基于这个式子可以得到。
c2应该是等于g 2+p二×c一。那我们把c1用刚才得到的这个式子进行一个展开呃,就可以得到这样的一个结果。好,接下来c3c4也是一样的,只不过就是层层的套娃,把它给套进去就行了。那需要再次强调,得到gi和pi的这些信息,我们刚开始就有因为AI表示的是倍加数。BI表示的是加数被加数和加数的完整信息,它们的每一位值到底是多少?我们刚开始就可以知道。所以我们刚开始就可以同时输入的这些信息,就应该是被加数a1一直到最后一位an,
还有加数b1一直到最后一位BN的信息。另外还有以前的运算保留下来的c0最原始的这个进位信息,我也是我们刚开始就可以知道的。好,现在根据a1和b1,我们是不是可以得到g1和p1?那观察下面这些式子,我们会发现g1和p1在c2的这个运算当中。还有c3的运算当中。还有c4的运算当中。我们都是需要用到的,当然在c1的运算当中,我们也需要用到,所以g1和p1这两个比特的信息我们可以刚开始就把它送到后边这些全家器里边。好,
这是根据a1和b1得到的两个比特的信息。好,那接下来a2和b2呃经过运算之后,也可以迅速的得到g2和p2。那经过观察会发现,只要是角标大于等于二的这些呃进位信息的运算,都需要用到p2和g2。你看c3也一样p2。g2c4也是需要用到p2啊g2好,所以g2和p2这两位的信息需要送到后面那几个位的啊加法当中。好,那后续的推理都是一样的,我们通过加数和被加数。对应的两个比特位可以算出与之对应的gi和pi,那gi和pi的信息我们可以通过线路把它送往更高位的运算当中,
因为更高位的这些运算的。进位的确定需要依赖于gi和pi。好,所以这就意味着,即便我们要计算的是c4这个进位的信息,那么在刚开始,我们就已经把所有的计算所需要的这些数据,这些原材料给它准备好了。准备好了,那我们只需要根据这个表达式设计相应的电路就可以在第四个全加器那儿直接算出c4的值。也就是说,采用这种策略的话,那么每一位的进位都几乎是同时产生的。我们不需要再像之前那种设计方案那样,需要等待着后面的进位一位一位的往上传。
好,所以这种加法器的速度会快很多,我们把这种加法器称为并行进位的并行加法器。因为每一个进位信息都是并行的,产生的几乎可以在刚开始就同时产生。所以这种经纬方式又可以称为先行经纬和同时经纬。好,那接下来我们再来看这种设计方案的一个不足,我们从c1C2C3c四一路往下套,娃套的越深,那你会发现这个逻辑表达式变得越长。那如果继续往下算的话,是不是c5c六会变得越来越复杂?逻辑表达式本质上其实就是我们电路的一种数学化的描述。逻辑表达式越长越复杂,
那是不是就意味着我们的电路设计也会越来越复杂好?所以我们这个套娃的过程应该适可而止。那比较经典的一种做法是,我们会套到c4这一位,也就是我们可以同时支持啊,四位加四位的一个运算。在这个加法器内部,每一个进位都是同时并行的产生的。就像刚才我们分析的那样,我们只需要由四个fa,也就是四个全加器,再加上一些新的线路,然后加上一些运算逻辑啊,运算逻辑呢,就是根据我们这儿的表达式来设计的。
加上这些东西,我们就可以构成一个四位的cra加法器。这个小节的内容确实是比较硬核,在学习祭祖这门课的时候,大家一定要保持心平气和,心如止水。可能你会自闭,但是你终究可以想开,因为这不是重点。这个小节的内容,不管你掌握的怎么样啊都行,没事没关系好的,那以上就是这个小节的全部内容。
2.2.2定点数的移位运算
那这一小节中,我们来学习定点数的移位运算怎么实现?那移位运算又可以进一步的划分为算术移位逻辑,移位还有循环移位。我们会按从上至下的顺序,依次讲解好那首先来认识一下什么叫做算术移位。我们从大家熟悉的十进制数出发。呃,假设这儿有这样的一个十进制数九八五点二幺幺,那么我们从小经常做的一个事情是让小数点后移一位或者后移两位。那小数点每后移一位,相当于啊,我们对整个数值乘以了一个十的一次方,也就是奇数的一次方,而小数点后移两位,
相当于乘以十的二次方。那小数点往前移,也是类似的一个效果,只不过往前移的话,那相当于是除以十的一次方和除以十的二次方,这是大家在小学的时候就学会的东西。那如果结合我们之前对r进制数,它的实际数值的一个定义来看的话,其实我们移动了小数点之后。相当于改变了每一个数码位的位权,因为每一个位的位权为多少,其实是以小数点的位置作为参考的。所以所谓的算术移位,算术移位的意思就是我们通过改变各个数码位和小数点的这种相对位置,从而改变各个数码位的位权。
那我们可以用这种算术移位的方式来等价的,实现乘法和除法。这是大家熟悉的十进制,那对于我们之前的小节中学习的二进制数,其实也是一样的。对于定点数来说,我们没办法改变小数点的位置,但是山不转,可以让水转,所以我们如果能够移动这个数值部分,只要能改变每一个数值位和小数点。点的相对位置关系,那我们同样可以实现算术移位的运算,比如这儿,我们已经有了负20这个数的源码,
表示好来看一下,进行了算术右移右移了移位之后。啊,得到的这个值应该是二的一次方,再加上二的三次方,也就是等于十,那么在考虑上这个符号位就应该是负十这样的一个值。所以和我们之前十进制推出的结论是类似的,当我们对二进制的这种定点数右移一位之后。相当于我们实现了除以基数的一次方,这样的一个操作。那结合这个图并不难理解,本来以前这两个一它们的权重分别是二的四次方和二的二次方,那右移一位之后它们的权重都分别除以了二一个变成了二的3倍。三次方一个变成二的,
一次方整体都是这样,缩小了一半,所以这就是算术右移的一个效果,相当于除以二。好,我们再右移一位,那么刚才这个最低位就会移动到呃小数点的,后面这个位置由于我们的机器字长有限,所以移出去的这一位,我们就只能舍弃不用。那同样的新的这个高位,我们会用零来补充,那这次右移的结果同样也是相当于再除以一个二。好,继续在这个基础上,
我们再右移一位,那这个时候我们会把末位的一给移出啊,这八个比特的范围。同样高位补零,不过这个时候得到的值啊,它应该是负二这样的一个值。已经不是负五÷2的精确表示了,因为负五÷2应该是负的二点五,这样才是精确的除以二的值。那这是因为我们移出去的这一位,它的值不是零,而是一,所以其实相当于我们舍弃了二的负一次方这样的一个精度。因此,我们得到这样的结论,
当我们进行算术右移的时候,首先高位会用零来补充,然后低位直接舍弃,如果我们舍弃的这一位是零的话,那么就相当于。于严谨的除以二的一个结果,而如果我们舍弃的这一位不等于零,那在这种情况下,我们会丢失一定的精度。好,这是算数右移,接下来来看算数左移,那左移的方式也是一样的,我们只让这个数值部分进行移动。符号位是保持不变的,
那进行左移之后,原本数值位的最高位会被我们舍弃,而最低位出现的这个空位,我们会用零来代替。那进行这样的一次右移之后,得到的结果应该是负的40,相当于在原有的基础上乘以二。那如果再左移一位也是类似的效果,我们丢弃最高位的零,然后相当于在原有的基础上再乘以一个二。好,我们再左移一位,由于这次我们丢掉的最高位,它是一,所以最终我们得到的结果,
这儿等于负的32。这次左移操作就不是呃乘以二这么简单了,这一点其实也很好理解,因为我们原本左移两位的时候,这个数值就已经到了负的80这样的一个。值那负的80,如果让它再乘以二,应该是负的160这么多,而这个地方我们源码的尾数只有七位。七个比特位只能表示零到127这样的一个绝对值的范围,所以160肯定是已经超出七个比特位能够表示的范围。那这种情况下,我们丢弃最高位的一全值,最高的那个一被我们丢弃了,当然就会出现这种严重的误差,
好,那这是对源码的算术左移需要注意的地方。好的,那目前为止我们探讨的啊,算术左移和算术右移是基于用源码表示的定点整数来探讨的,那如果不是定点整数,而是定点小数,其实也是一样的。一样的道理,一样的效果。当我们进行算数左移的时候,同样相当于乘以二的一个效果,算数右移相当于除以二的一个效果。因为所有的这些一它们的位全在左移和右移的时候,分别会乘以二和除以二。
好,所以定点小数,我们就不再单独的探讨好,那接下来我们再来看基于反码的算术移位,这儿我们已经给出了正20和负20的一个原码表示。那由于正数的反码表示和原码表示是一模一样的,所以对于正数的算术移位,不管是左移还是右移处理的方法都是和原码一样。这是反码表示的正数啊,需要采取的一个策略和原码是一样的,好再来看负数,也就是符号位为一的数。这种数的反码尾数部分和原码是完全相反的,一变成零零会变成一反码的,一相当于原码的零。
反码的零相当于原码的一,所以对复数的算术移位,我们进行补位的时候需要注意,我们补的都是一。那这是对于反码的算术移位正数和负数,我们需要补充的这个位是不一样的,好接下来我们再来看补码的算术移位。那由于正数的补码表示和原码也是一样的,所以对于正数补码的移位运算,我们同样和原码保持一样的策略就可以。都是需要用零来补充移动之后出现的空位。而负数补码的移位会相对来说复杂,一些补码是从反码的基础上末位加一得到的。那反码的末位加一会,导致反码当中更靠后的这些一都会变成零,
并且都会发生进位,直到进到第一个零这个地方为止。所以反码转补码有这样的一个规律,就是从反码的最右边这一位开始,从右往左依次的取反。把一都变成零,直到碰到第一个零为止,把反码的第一个零变成一之后,再往前的这些部分就不用再改变了。所以负数的补码呈现出了这样的一个规律,在这个补码最右边的一个一,还有这个一。再往右的这些部分,这些部分是和原码保持一致的,而最右边的这个一左边的这些部分,
这些部分又是和反码保持一致的。所以,当我们对补码的这些尾数进行算数右移的时候,往右移会导致高位出现一个空位。那我们补这个空位的方法,应该是和反码的啊,补空位规则是保持一致的,也就是补一。而当我们对补码进行算数左移的时候,最低位会出现一个需要补的空位,那由于补码的后半部分和原码是相同的,所以我们在补这个空位的时候应该补。零因此,我们得到结论,对于负数补码的算术移位,
当右移的时候,我们应该补一,然后低位舍弃。当进行算术左移的时候,应该低位补零,然后高位舍弃。那用这样的方式,我们就可以保证对补码的算术右移。同样是相当于除以二的一个效果,算术左移相当于乘以二的一个效果。好的,那我们再对算术移位进行一个小小的总结,对于正数来说,由于原码补码反码啊,它们的正数表示都是一样的,
所以我们需要补位的时候都是用零去补。而对于负数来说,补码的负数左移的时候需要添零,右移的时候是要添一,而反码的负数不管左移还是右移,我们都是要添一。那只要遵从这儿给出的规定,无论我们使用的是什么码,只要我们进行的是算术左移,就相当于乘二,只要是右移,就相当于除以二。只不过由于我们机器字长呃位数有限,所以有的时候我们没办法用算术移位精确的来等效乘除法,这点我们在讲源码的左移和右移的时候特别的强调过。
好,接下来用一个简单的例子让大家体会一下算术移位的一个啊,具体的应用我们之前已经探讨了负20这个源码,它左移一位和左移两位所得到的一个结果。好,那如果说现在我们要算的是负20×7,要按计算机完成这个乘法运算。那七这个数我们可以把它拆分为二的,零次方加二的,一次方加二的,二次方,所以负20×7,我们可以把它拆解为这样的三个乘法。那硬件在执行乘以七这个过程的时候,其实就相当于把负20这原有的数的基础上不移位,
还有左移一位左移两位啊,这样的三个数进行一个。相加的操作就可以等效的完成负20×7的操作。所以计算机硬件实现乘法,其实是基于算术移位,还有加法来进行的。而实现算术移位的硬件电路设计起来并不复杂,好,那具体乘法怎么实现?我们还会用之后的小结进行更进一步的探讨。接下来我们再来看第二种移位叫做逻辑移位。那逻辑移位的规则很简单啊,当我们右移的时候高位补零,然后低位移出的这一位直接舍弃就可以。左移的时候,
我们在低位补零,然后移出的这一位,我们直接舍弃就可以。那逻辑移位的这种规则,我们可以把它看作是对无符号数的算术移位好,这个规则很简单,那我们来看一下逻辑移位有什么作用?比如说啊,我们在计算机里边表示一种颜色的时候,经常会使用到这样的一种表示方式,叫做rgb。那r指的是red,也就是红色g,表示的是green,也就是绿色,
而b表示的是blue。也就是蓝色,因为我们知道自然界里边所有的颜色都是由红绿蓝这三种三原色来按照一定的配比来组成的。比如,对于最后这个颜色啊,它的rgb值就分别是幺零二幺三九和幺三九。那有的时候我们存储一个像素点,它的rgb值的时候需要把rgb这三个值把它们连成三个字节的一个整体。第一个字节存放r,第二个字节存放g,第三个字节存放b的值,而现在我们只是分开指明了这三个部分的值分别是多少?好,那来看一下怎么把它们拼成三个字节的一个整体,可以这么做,
首先我们呃申请用三个字节来存储无符号数一零二,也就是r的值。那现在我们对这个无符号数进行逻辑左移,左移16位。这就会导致低八位移动到高八位的位置,然后左移产生的这些空位,我们都是用零来补充,这是逻辑左移的一个规定。好,接下来我们再定义一个三个字节的无符号数啊,这个数的值是幺三九。那么,我们再对这个无符号数进行逻辑左移,八位的操作就会导致原本的第八位被放到了中间的八个位的位置。好,
最后我们再用三个字节来存储,无符号数,也就是b的值幺三九好,最后我们再把刚才得到的一二三这三个部分进行一个加法操作。就可以得到三个字节表示的rgb值最高的一个字节,表示的是r值。中间表示的是g。也就绿色的值,然后最后表示的是b blue的值。好,那这是逻辑移位的一个应用的小例子,接下来我们再来看最后一种移位运算,叫做循环移位啊,顾名思义,所谓循环就是指。
当我们进行,比如说循环左移的时候,移出来的这一位会被放到我们需要填补的这个空位。在移位的时候,整个二进制串是进行循环补位的,这个应该很好理解,那当我们进行循环右移的时候,是不是也是类似的从右边移出来的这一位?又会跑到呃应该补充的那个位置去好,那还有一种比较特殊的循环移位就是带进位位的,这种情况先来解释一下什么叫进位位?也就这我们标注的CF这一位进位的概念,大家都知道了,比如我们对两个八位的二进制数。进行加法操作,
那当我们运算到最高位的时候,一+1是等于零往高位进一,但是由于机器字长有限,寄存器里只能保存八个二进制位。而最高位又确实产生了一这样的一个进位,因此为了实现超过八比特的这种呃数据的加法。计算机硬件里边会包含这样的所谓的敬畏的畏来,记录下之前这些低位的运算有没有产生敬畏。把这个进位保留下来之后,我们再进行之后的更高字节的运算的时候,就可以得到正确的结果,零+1再加上刚才保留的这个进位一。然后用这样的方式,我们就可以不断的往高位啊进行计算好,所以这就是所谓的进位位的一个作用。
总之,它里边要么存了一个一,要么存了一个零好,现在当我们考虑上这个进位之后再进行循环左移。产生的效果就是这样的。就是会把原本数值位的最高位把它放到这个进位位这个地方,而原本的这进位位会来补充出现的空位。这个样子好,这就是带进位位的循环左移,那带进位位的循环右移是不是也是类似的啊?无非就是把末尾的这个低位放到进位位的位置。而原本的进位位把它放到啊最高位,出现空缺的这个位置好,那这就是所谓的循环移位。那这个小节中,
我们学习了定点数的移位运算,其中最常考的是算术移位啊,当我们进行算术左移移位的时候,相当于乘了一个奇数。当我们算数右移移位的时候,相当于除以计数的效果,那原码反码补码进行移位之后补位的这个策略是不太一样的,特别是补码,大家比较容易忘,需要基于理解来记忆。那逻辑移位的实现很简单,无论是左移还是右移,都补零就可以,最后我们又学习了循环移位,就是用移出去的位来补上空缺的那个位。
而对于带进位位的循环,移位就是移出的位,我们会把它放到进位位的位置,而原来的进位位会补上空缺的那个位置。那再次强调,由于原码补码反码,它们的位数有限,也就是可以表示的数值范围是有限的,所以在某些情况下移位操作并不能精确等效乘法和除法的一个效果。可能会散失精度,甚至是产生比较大的误差。好的,那这就是移位操作相关的所有内容。
2.2.3定点数的加减运算
好的,这一小节中,我们要探讨的是定点数的加减运算,我们之前说过在进行加减运算的时候,我们通常只会探讨源码和补码。而反码一般来说是不会直接参与这种加减乘除运算的,所以我们只探讨原码和补码的加减运算。另外,当进行加或者减的时候,有可能会出现溢出的情况,那么计算机硬件是用什么逻辑来判断溢出的呢?这个是我们之后会探讨的问题。首先来看一下原码的加减运算,其实这个部分在之前的小结中,我们有简要的探讨过,
比如说负14和14这两个数进行相加的操作。那如果加法器,直接使用它们的源码进行相加,最终得到的这个结果是一个错误的结果。那我们的处理办法是把负14改为正14,然后加法变成减法,这样的运算用这样的方式来等价正14,加上负14的一个效果。最终就可以得到正确的结果,那对于源码的加法来说。左边是被加数,右边是加数被加数,有可能出现两种情况,一种是正数,一种是负数。
而加数也有可能出现正和负这样的两种情况,所以源码的加法操作有可能出现四种情况。就是被加数和加数分别为正或者为负的两两组合。好,那首先来看,如果说被加数和加数都是正数的话,此时我们只需要让被加数和加数的绝对值做一个加法。那相加的结果保持为正就可以,而如果是负数加负数,那么同样的用这两个数的绝对值做一个加法符号位呃,保持是负不变。如果被加数是正的,加数是负的,那么我们需要用绝对值大的一个数减掉绝对值更小的一个数。最终的符号位和绝对值大的一个数保持一致,
比如被加数,它是正的14,而加数是负的15。那么,我们丢给减法器这个硬件进行减法运算的时候,是把这两个数的绝对值丢给了它。并且是绝对值大的一个数,也就15作为被减数,然后减掉绝对值小的一个14作为减数。这样我们就确定了数值部分应该是一,然后最终的结果符号位应该是和绝对值更大的,这个数保持一致,也就是负的。好最后一种情况,一个负数加上一个正数,
那这种情况下处理的方式和上边其实是一样的。同样是要用绝对值更大的那个数减掉绝对值更小的来确定数值部分,然后符号位和绝对值更大的那个数保持一致。那源码的这种加法逻辑,如果用硬件实现的话,是很复杂的,接下来我们再来看源码的减法运算。其实减法运算,我们最终终归可以把它转变成与之等价的加法运算,我们只需要把减数的符号取法。这样的话,我们就可以把一个减法操作转变为呃,与之等价的一个加法操作,而转变为加法之后,就可以用我们之前提到的这个逻辑来进行处理。
好所以减法操作,这儿就不再多说,另外一点需要注意的是,在进行源码加法的时候,如果被加数和加数,它们的符号都是相同的。那么,加法之后得到的结果有可能出现溢出的情况。好,那这是原码的加法和减法的一个实现思路,减法只需要把减数符号取法就可以转变成与之等价的加法。那刚才我们说过原码的加法这么复杂的逻辑,用电路实现太难了,所以计算机当中通常是用补码来实现加减运算的。那这一点我们在之前的小节中同样提过。
对补码进行加减运算,我们不需要单独的考虑符号位应该怎么处理,符号位同样参与运算就可以好来看一下呃,现在有a和b这样的两个数分别是15和负的24。那如果用二进制表示它们的真值应该是这样的,两个数四个一表示的是15,然后一一零零零表示的是24。好,现在这个机器字长是八位,其中最高位是符号位,所以我们把a和b两个数补足八位,得到它们的源码。再根据源码,我们可以求得a和b两个数所对应的补码,那源码和补码的一个转换关系,
我们之前已经介绍过。好,现在我们要计算a+b的值,那我们只需要把a和b两个补码一起丢给加法器,然后加法器会让这个符号位也一同参与运算。好,那这样我们就得到了a+b的一个值的补码表示,那可以验证一下,我们把这个补码转变成原码,会发现它所对应的真值是负九。也就是15±24的一个正确结果,那还记不记得补码如何转变成原码啊?和原码转变成补码的方法是一样的。在补码的基础上,数值位全部取反,
然后末位加一就可以得到与之对应的源码,那其实还有一种更快速的转换方式。用我们上一小节啊,推出的这一个规律,对于一个负数来说,负数的补码当中。最右边的一个一,也就是这个一这个一,还有它的右边的部分。和原码是保持一致的,而这个一左边的这些部分和反码是保持一致的,所以我们把补码转变成原码,一个更快的方法是。找到补码最靠右的一个一,然后以这个一作为一个分界线,
这个分界线左边的这些全部取法。就可以直接得到源码。当然了,用之前大家熟悉的方式来转换也是okay的。好,那刚才我们探讨的是补码的加法运算,接下来来看一下补码的减法运算如何实现,那减法必然是可以转换为加法的。a-b可以把它等价为a,加上负b,所以我们只需要根据b的这个补码来得到负b的一个补码就可以。那这个转换方式我们也强调过,只需要把所有的位就是符号位在内全部取反,然后末位加一就可以。当然,
你也可以结合我们刚才介绍的这些方法,要由b的补码求出,负b的补码,那由于b是一个负数,所以我们可以先求出这个负数的一个原码。那从右往左找到第一个一这儿画一条分界线。第一个一的左边所有的部分全部取法零零零一,然后这个一的右半部分全部保持不变。这样的话,我们就可以直接得到负b的一个补码,表示和我们所有的位取反,然后末位加一啊,得到的结果是一致的。好,那这儿我们得到的值,
它是一个补码表示把它转换成真值的话,就应该是正的39。这就是我们所期待的正确结果,补码的加法和减法的实现,最终都会转变成加法,这样的话,我们只需要设计一个实现加法的硬件,加法器就可以,不需要设计减法器。两个数的补码进行相加操作的时候,符号位也会参与运算。好,接下来给大家一个题,大家可以暂停来练练手,我们引入一个新的变量c,
然后a和b就是我们之前给出的这两个值。大家可以试一下a+c和b-c的一个补码运算的结果,可以暂停试一下。好,我们来看一下a+c最终计算的结果是这样的呃,这是c的一个补码。那这个补码所对应的源码是多少呢?我们从最右边这个一的这儿画一条竖线,然后前边这些数值位全部取反。最右边的这个一还有它再往右的部分保持不变,符号位同样保持不变,那这就是这个补码所对应的源码,大家可以自己算一下。增值是负的,117。
好,现在问题发生了a=15 c=124,但是计算的结果两个正数相加,竟然得到了一个负数。那这种现象说明发生了溢出,因为124+15应该是等于139,而我们之前说过。八位的补码,它所能表示的范围只有负的128到正的127这样的一个范围,所以139这样的一个增值显然已经超出了八位补码。可以表示的范围,所以这种情况下就是所谓的溢出好另一个例子b-c,其实也是一样的,我们通过c的补码求出负c的一个补码。这样的话就可以把b-c转变成加法运算,
那最终得到的这个结果可以看到啊,对应增值是108,同样也是发生了溢出的问题。负的24减掉124已经超出了八位补码,所能表示的范围两个负数相加,竟然得到了一个正数。好,那既然溢出的状况不可避免,因此计算机硬件就必须考虑到如何处理溢出的问题,所以接下来我们要探讨用计算机硬件实现溢出判断的一个逻辑。当我们对补码进行加法或减法运算的时候,最终都会被转变成加法运算,所以我们只需要考虑补码的加法运算如何判断溢出就可以。好那溢出分为这样的两种,一种叫上溢,
一种叫下溢啊,根据这个图很好理解一个补码,它所能表示的范围是有限的。比如八位的补码就是负的128,一直到正的127这样的一个范围,那如果两个正数相加。最终的增值超出了127的范围,此时我们就称发生了上亿。那类似的,如果两个复数相加,最终得到的增值呃小于负的128超出了复数区所能表示的范围,那此时就发生了所谓的下移。当发生上亿的时候,一定是两个正数相加,最终得到的结果看起来是一个负数。
而发生下移的时候,一定是两个负数相加,最终得到的结果看起来是一个正数,也就是我们刚才提到的这两种情况。那我们可以基于这个规律来设计判断溢出的逻辑,给大家一个例子,大家可以自己体会一下,这儿给出了三位的补码。所能表示的范围也就负四到正三这样的一个范围,并且这个地方已经给出了每一个数值所对应的补码到底是多少?那首先第一点只有两个符号,相同的数相加的时候才会发生溢出,这点应该是很好理解的,因为如果两个数,比如说一正一负进行相加操作的话。
那最终得到的结果只有可能比被加数和加数的绝对值更小,所以两个异号的数相加不可能发生溢出。好,现在结合这个三位补码来验证一下,当发生上亿的时候,是否一定是正数加正数,然后最终得到一个负数呢?来看一下三位补码,最大可以表示的正数是三,如果两个正数相加超过了三,这个值,那么此时就发生了上亿。比如说二+2,那么二的补码是零一零,这两个数相加应该是等于零零往高位进一个一。
也就是幺幺零,那这个补码对应的是负四这个值。那你会发现从这个数轴上看,在二的基础上再加二,其实相当于从二为出发点往右移动一格,然后再移动一格。移动两格来到了负四这个位置。所以二+2发生了上亿,最终得到的结果是负四,这和我们之前给出的这些结论是相符合的,再来看,如果是三+3。那么就是零一一加上零一一啊,得到的结果应该是零一一。也就是负二这个值那在三的基础上再加三,
如果结合数轴来看的话,就是从这个点出发向右移。一格两格三格移动到了负二这个地方。同样的两个正数相加,得到了一个负数,那么此时我们就可以判断是发生了上亿的情况。好,所以结合这个例子,我们可以体会到当发生上亿的时候,一定是正数加正数,最终得到的结果是一个负数。而发生下移的时候,大家也可以自己验证一下,一定是负数加负数,最终得到的结果是一个正数。
好,那基于这个规律,计算机硬件如何判断溢出呢?先来介绍第一种方法,采用移位符号位。计算机进行溢出判断的逻辑表达式是这个样子呃,其中as表示的是被加数的一个呃正负号。而BS表示的是加数的一个正负号,而ss表示的是最终运算结果的一个正负号。当这个逻辑表达式计算结果为零的时候,表示没有溢出为一的时候,表示有溢出好,现在我们来解释一下这个逻辑表达式的一个含义。这可能是跨考同学的一个盲点,那如果大家学过离散数学,
肯定学过这种逻辑表达式呃,除了离散数学之外,数字电路。呃数电这门课里边也会接触到这种逻辑表达式。不过,跨考的同学可能没有学过这些内容,所以我们来解释一下,照我们as BS和ss表示的是逻辑值的真和假,或者说零和一。以这样给出的第一个加法为例。as表示的是,被加数的一个符号,也就是零表示逻辑值的假,然后BS。表示的是加数的一个符号,
同样是零表示逻辑值的假,然后最后ss表示的是最终运算结果的一个符号位。符号位为一表示逻辑值的真,也就是这三个逻辑值分别为假假真。只不过这个地方,我们把这几个逻辑值用这种连着写的方式把它们写在一起,表示的是。这些逻辑值之间执行一个与的运算。那这儿的语运算其实就是大家熟悉的C语言里的且的一个操作。几个逻辑值进行与运算的时候,只有所有的逻辑值都为真,都为一的时候,最终相与的结果才是一。只要其中任何一个为假,最终都会等于假。
跨考的同学可以联想一下,你编程的时候写if语句,你是不是会写啊?某某某条件。且某某条件。再且。某某条件对吧?只要其中的某一个条件不成立,为假的时候,那么这个逻辑运算最终的结果肯定都是假,只有所有条件都满足,都为真的时候最终得到的结果才是真。那这是与运算的一个逻辑。好在这个式子当中,除了把这些逻辑值并排着写之外,
呃,这儿还有一个加号,但是这个加号表示的是逻辑的或运算。也就是大家编程的时候会用到的,或这个运算符呃,某条件或某条件。再或某条件,如果多个条件进行或运算的话,那么只有所有的这些条件都不成立,都为假啊,或者说都为零的时候。这个或运算的结果才是零,而只要其中一个或者多个呃条件能够满足为真。或者说为一的话,那这个或运算的结果就是一。
好,最后这个式子当中还会涉及到逻辑非运算,在一个逻辑值的上面画上一横,表示一个非运算。其实非运算就是大家写代码的时候会用到的,是感叹号,感叹号,然后某一个条件对吧呃,其实就是指这个条件。真假取一个反,这就是非运算。它的效果就是零变一,一变零好,那弄清楚与或非这三个基本的逻辑运算之后来看一下这个逻辑运算的式子,最终得到的结果。
基于这儿的第一个式子被加数和加数的呃符号位都为零。然后最终得到的结果符号位为一,而最终的这些结果。需要进行一个非运算,也就是会把一变成零。好,那左边这一整块的运算就是三个零进行与运算,那显然这个与运算最终得到的结果是零。再来看右半部分,本来是零零一好,现在as和BS会分别进行一个非运算。零会变成一那这样的话,右边部分就相当于是三个一的与运算,那右边这个整体运算结果是一。好,
最后这两个羽运算再进行一个或运算。零或一应该是等于一,所以基于刚才给出的这些加法,我们用这个逻辑判断就可以得到v的最终结果是等于一。此时表示是有溢出发生的。那第二个式子的逻辑运算跨考的同学也可以自己带进去验证一下v同样可以得到一。当有溢出发生的时候,也就是v=1的时候,一定是左边这个部分运算的结果等于一或者右边这个部分运算的结果等于一。那当加数和被加数的符号位都为一,最终运算的结果符号位为零的时候,就会导致左半部分运算结果为一。而如果加数和被加数呃符号位都为零,最终运算的结果符号位为一,此时就会导致右半部分的运算结果为一。
其实这个式子背后的逻辑依然是我们之前提到过的东西,如果两个负数负加负,最终等于正。那么,这种情况下是有溢出的,如果正加正等于负,此时也是有溢出的。那既然背后的逻辑这么简单,我们为什么还要折腾写出这么复杂的一个逻辑表达式呢?原因是这样的,与或非这些逻辑运算。可以很方便的用一个门电路就实现与门或门非门就是分别实现与或非的。所以只要我们用数学的方式写出了这个逻辑表达式,其实就相当于我们已经把啊硬件的电路都给设计好了。好,
所以这个地方折腾这么多,其实我们就是在设计硬件电路的实现,只不过我们是用数学表达式的方式把它表示出来而已。好的,这是硬件判断溢出的第一种方法,接下来看第二种实现方法同样只采用一位符号位,但是我们会根据数据位的进位情况,还有符号位的进位情况来进行溢出。的判断,当符号位的进位为零,然后最高数值位的进位为一的时候,此时发生了上亿。而符号位进位为一,最高数值位进位为零的时候。说明此时发生了下移好,
我们来解释一下什么叫符号位的进位和数值位的进位。同样的,我们结合这儿给出的例子好,现在计算a和c补码相加的一个值呃末位一+0等于一一+0=1。一+1=0向高位进一个一,那一+1再加一=1向高位进一个一,一+1=0向高位进一个一。一+1=0向高位,再进一个一好,现在我们已经算到了数值位的最高一个位。这个位等于零+1再加一,那么应该是等于零,然后往高位进一个一。这就是所谓最高数值位的进位,这儿是进了一个一好,
接下来是符号位的运算,零+0再加一应该是等于一。那上一小节中,我们是不是提到过近位位的一个问题?当a和c相加的时候,近位位应该是等于零。也就是这儿所谓符号位向更高位产生的一个进位CS好,所以在这个例子当中CS=0,然后c1=1。此时一定是发生了上亿,那第二个例子大家也可以验证一下,这两个数相加,最高数值位的进位应该是零。然后符号位往更高位的进位应该是一。两个负数相加,
最终得到的结果是一个正数,而原本的符号位往更高位又进了一个一。此时说明发生了下移。好,所以只要发生了溢出,那么刚才我们提到的这两个位的数值肯定是不同的。那怎么用硬件来判断不同的逻辑呢?只需要用我们之前提到的异或运算就可以。计算机硬件只需要把符号位的进位和最高数值位的进位进行一个异或运算,如果异或的结果为零,那么表示没有溢出异或的结果为一,则表示有溢出。因为之前我们说过异或的逻辑就是如果两个逻辑值不一样,那么异或的结果为一,而如果两个逻辑值相同,
那么异或的结果都是零。这样的话就符合之前我们给出的判断逻辑。好,那这是用硬件判断溢出的第二种方法。好,再看最后一种用硬件判断溢出的方法,我们可以采用双符号位的补码来表示一个数正数的符号位是两个零负数的符号位是两个一。之前我们介绍了补码,都只有一个符号位,现在我们把符号位进行一个拓展,让它有两个符号位,并且两个符号位一定是零零或者一一。好,我们来试一下,用双符号位表示的a和c进行一个相加的操作,
同样的两个符号位都会参与我们的加法运算,同样要考虑进位这个问题。那最终得到的结果是这个样子,也就是运算结果的两个符号位,一个为零,一个为一。此时,我们可以断定,是发生了上亿,而第二个例子,两个复数,用双符号位补码的形式来表示,相加之后,呃,最终得到的结果,
符号位为一和零。两个符号位不一样,那么此时说明发生了下移。在最终得到的运算结果当中,更高的这个符号位表示的是本来应该得到的正确的一个正负性。而第二个符号位表示的是实际得到的一个正负的结果,所以本应得到正的,而实际得到了负的,那此时一定是上亿。本应得到负的,但是运算的结果是一个正的,此时可以判断发生了下移好,所以如果要用硬件来判断溢出的话,同样的,我们只需要用一个异或运算。
把运算结果的两个符号位进行一个异或如果异或的结果为零,也就是两个符号位都相同,那么说明没有溢出异或的结果为一说明有溢出。那这儿给出的是一个有溢出的情况啊,大家也可以结合之前的例子来,自己动手验证一下啊,如果说没有发生溢出的话,那最终得到的两个符号位一定是相同的。这儿就不再赘述好,那我们还需要补充一个关于双符号位补码的呃,一些一些边角的知识点。双符号位补码,又称为摩四补码,而之前我们学习的单符号位的那种补码,又称为摩二补码。
可以这么理解,如果把双符号位的补码,这个逗号把它看作是小数点的话,那么它前边这一位的呃全值应该是二的零次方。再前面这一位应该是二的一次方,那如果再往前还有一位的话,应该是二的二次方,也就是四。而之前我们简单的介绍过模运算,如果模四的话,其实是相当于把位全小于四的,这些部分给保留,而位全大于四的,这些部分全部把它舍弃。所以如果把这个地方看作是小数点的话,
那由于我们只保留小数点,前边的两位。所以这就相当于计算机在运算之后进行了一个模似的操作,那单符号位被称为摩尔补码的原因也是类似的,如果把这个地方看作是一个小数点。那么,它前边这一位的位全就应该是二的零次方,再前边如果还有别的位,那应该是二的一次方。那摩二就相当于把位全小于二的这些部分给保留,而位全大于或者等于二的啊,这些部分全部给舍弃。所以单符号位的补码又称为摩尔补码。好,那另外一点需要补充的是,
虽然我们这儿看到的这个补码,它有两个符号位,但是其实这种补码在内存里,在计算机当中进行存储。实际也只存储一个符号位,只不过在这个补码进行运算之前,在运算之前会复制一个符号位。然后接下来补码的运算会让两个符号位同时参与运算。所以这种双符号位的补码并不会增加存储所需要的空间。好的,那这就是关于溢出判断的三种方法,好的,那这个小节的内容比较多,我们介绍了定点数的加减运算怎么实现?对原码的加减运算的实现啊,
相对来说会复杂一些,所以实际当中通常使用补码的方式来实现呃加法或者减法。那对于补码的减法操作,最终我们肯定可以把它变为等价的加法操作,并且最后符号位会参与运算,符号位也要考虑到进位相关的问题。那对补码进行加法运算,有可能会导致溢出的问题,如果两个同号的数相加,最终得到的结果符号改变了,那么就说明发生了溢出。超出补码正数所能表示的范围,这种现象称为上亿超出补码负数所能表示的范围,这种现象称为下亿。那我们介绍了三种用硬件判断溢出的方法,
那在介绍双符号位的时候,我们也引入了两个概念,叫做摩四补码和摩二补码。这两个概念,大家也需要注意好的,那以上就是这一小节的内容。
2.2.4_1原码的乘法运算
好的,经过之前小结的学习,我们已经知道了定点数的加法减法,还有移位运算如何实现,那这小节中我们要学习定点数的源码乘法如何实现?那由于今天窗外的雨下的很大,所以可能会有一些雨声的录入好的,那这一小节中我们会首先探讨乘法运算的实现思想。然后介绍源码的移位乘法如何实现,最后还会介绍补码的移位乘法如何实现,那由于内容比较多,所以我们把源码和补码的呃乘法运算拆分成两个视频。那首先来看一下,大家比较熟悉的十进制的乘法,如何实现我们回忆一下小学时候如何做一个乘法运算的。
比如说零点九八五×0点二幺幺,如果算上小数点之前的这个零,那么这两个数都是四位。都有四个呃数码位,那首先是不是一乘以九八五,然后把它写在下面,然后接下来一乘以九八五?但是这次的九八五要和上一个九八五进行一个错位,相对于上一个九八五来说要往前移一位。好最后二乘以九八五应该是等于一九七零,那同样的这个一九七零的最后一位又要比上一个九八五再往前挪了一位。每一个数码位进行乘法运算,得到的这个结果,我们是把它们错位的排列在一起的,然后最后我们会把它进行一个相加得到,
这样的一个结果。然后最后小学老师会告诉我们如何处理这个小数点,那小时候我们老师教我们的做法是从最后这个位置往前数六位。一二三四五六那就到了二的前边,所以小数点最终确定在二的前面这个位置。那这就是零点九八五×0点二幺幺的一个手算方式,这是我们小学时候学过的东西,那现在的问题是这样的。呃,不知道大家有没有思考过,为什么我们在写这些?每一位承德的结果的时候都要错位的,把它们写在一起。其实这个原因可以结合我们之前介绍过的二进制的这个数值的定义来进行理解,零点二幺幺这个数我们可以把它看作是二×10的负一次方。
然后加上一×10的负二,再加上一×10的负三,然后零点九八五,我们不妨把它看作是九八五×10的负三次方。所以这两个数相乘,我们可以把它拆分成这样的形式,九八五×10的负三次方,再乘以一×10的负三次方,也就是九八五×1×10的负六次方。这一项再加上九八五×1再乘以十的负五次方,也就是和这儿的第二项的一个结合,那最后这项也是一样的。所以其实这个式子,如果我们把中间加和的这些部分给它扩展一下的话,其实它是长这个样子。
九八五×1再乘以十的负六次方,那不就相当于九八五从这个位置小数点往前移六位嘛,然后九八五×1再乘以十的负五次方,那就相当于从九八五这个数这儿。把小数点往前挪,五位那最后这个数也是一样的,九八五×2等于一九七零,本来小数点在零的后面乘了一个十的负四,那就相当于往前挪四位。所以我们最终要给这几个小数进行一个加和,我们是不是得保证这个小数点是相互对齐的,而小数点相互对齐就会导致我们后边这些数?有这样的一个错位的规律,所以这就是为什么我们小时候学的乘法,需要把每一位的位积进行一个错位的原因。
这才是它的本质好,那这是我们熟悉的十进制乘法,接下来我们把这种乘法的思想迁移到二进制的乘法。同样先来看手算的方式,假如有这样的两个二进制数的相乘。两个定点的正小数进行相乘,并且每一个定点数占五位,那类似于十进制零点九八五×0点二幺幺的乘法。首先,我们应该用乘数的最低位啊,乘上这个被乘数得到的结果应该是幺幺零幺。然后再用乘数的下一位次,第一位乘以这个被乘数那得到的结果,同样是幺幺零幺,不过我们要呃把它往前挪一位。
进行一个错位好,接下来零乘以这个被乘数,那得到的结果应该是四个零,最后是一乘,以这个被乘数得到的结果是幺幺零幺。那把它们错位,并且依次相加之后,可以得到这样的一个结果,好接下来确定小数点的位置,应该是从最后这个位置往前移八位,也就是刚好到了一的前面。那原因和十进制是一样的,十进制最终得到的结果,我们在确定小数点的时候采用的最原始的方法是呃,看一下呃,
这个被乘数它应该是小数点往前移。一二三三位,然后这个乘数的话,又是从这儿开始往前移,四五六六位,所以这两个数加起来总共需要从最后这个位置往前移六位,用这样的方式来确定小数点的位置。那二进制的确定方式也是一样的好,现在我们使用和之前类似的思路,把这个式子啊,进行一个完善。其实,每一位和被乘数的积进行错位相加的原因是这样的。我们把乘数按位全进行展开,然后把被乘数乘数各个项分别进行相乘相加,
进行一个展开,那么就会得到这样的一个式子。第一项对应于幺幺零幺乘以二的负八次方,也就是最低位的这个位积,然后第二个部分。是对应了次低位的危机,第三部分成了一个零,但是我们还是把它统一的写成零点零零零这样的一个形式。保持队形一致,那最后这一项也是类似,所以我们模仿十进制得到的这种乘法规则,其实是正确无误的,我们把它展开就可以看到背后的一个逻辑。好,所以二进制的源码乘法实现起来要比十进制还要方便,
因为二进制的乘数每一位只有可能出现零或者一这样的两种情况。如果出现一,那么这次得到的这个位积就刚好和被乘数是一样的,如果出现的是零,那么这次得到的位积,我们直接取全零就可以,只有可能出现这两种情况。另外,在这个式子里面,我们乘以什么二的负八,二的负七,二的负五,这样的运算用计算机是不是很方便实现?幺幺零幺乘以二的负五其实就相当于把幺幺零幺右移五位,对吧?
因为左移等价于乘,右移等价于除,那乘以二的负五次方不就是除以二的五次方吗?也就是向右移五位。因此,每一位和被乘数相乘所得到的这个位积,我们可以很方便的用移位运算来实现。好,现在我们已经模拟了手算的一个思想,接下来我们尝试着用机器来实现这些乘法,那刚才我们还有一些没有考虑到的问题。第一,我们这个例子当中是用两个正的小数进行相乘,但是实际的数字肯定有正负之分。所以符号位我们应该如何处理呢?
这是第一个问题,第二个问题,我们五位的这个呃,两个定点小数相乘之后的结果。有可能到达九位这么多。几乎是翻了一倍,那如果计算机的机器字长本来就只有五位,也就是每一个寄存器只能存放五位的呃数据那。那我们最终得到的这个沉积已经超出一个寄存器可以保存的这个容量,那这个问题又要怎么处理呢?然后第三个问题,刚才我们这个例子当中乘数的每一位和被乘数相乘所得到的位积。是不是都要保存下来,然后最后再来统一的相加,也就把这四个数分别的保存在四个寄存器里?
最后再来进行统一的相加。显然,这种方式是不靠谱的,因为我们这儿进行举例的两个数,它的长度还不是很长,如果说是两个64位的二进制数进行相乘。那就意味着会有64个未机,那这是不是意味着我们需要有专门的64个寄存器来分别保存这64个未机?这显然是比较浪费的一种行为。所以这个问题又要如何处理好?接下来我们带着这三个问题来寻找答案。我们来看这样的一个例子讲。假设计算机的机器字长为n+1位,也就是五位,有一个符号位,
然后数值位是n位。现在有x和y这样的两个数,这儿分别给出了原码x是一个负数y是一个正数,然后要用原码的移位乘法来求得x×y的一个值。好,之前我们已经探讨了两个源码正数相乘,应该怎么计算?那我们可以沿用之前这种思路。符号位我们可以单独的处理啊,处理的方式很简单,就是把两个数的符号位进行一个异或运算就可以确定它们的乘积到底是正是负。然后用被乘数和乘数的绝对值来进行乘法运算,这样的话是不是就转换成了我们之前提到的正数乘以正数的这种乘法运算的思想?那我们把x和y的绝对值的一个源码给它写出来好,现在回头来解释一下这个符号位为什么是这么确定的?
还记得异或的规则吗?异或的规则就是如果参与异或运算的两个二进制位。相异不一样,一个是零,一个是一的话,那么最终异或的结果就是一,而如果两个二进制数相同。比如说零和零异,或或者一和一异,或那么最终得到的结果都是零,那由于正数的符号位是零,负数的符号位是一。正和负相乘得到一个负数,正和正相乘或者负和负相乘都会得到一个正数。因此,
用异或的逻辑来确定呃乘积的一个符号位是没有问题的。所以符号位的确定很简单,接下来我们来看这两个绝对值的相乘如何用机器来实现好,现在我们来回忆一个很远古的PPT。还记不记得在第一章里边,我们在聊到运算器的基本组成的时候,给过这样的一个表,我们运算器里边有a cc mq和x,也就是通用寄存器。有这样的三个必不可少的寄存器,当我们进行乘法运算的时候,ACC这个寄存器里边存放的是沉积的高位。而mq里边存放的是乘数,还有乘积的低位,然后x这个通用寄存器里边存放的是被乘数。
好,现在我们来深挖一下背后的一个运算的过程,首先x这个通用寄存器里面要存被乘数。那么,被乘数就是x的绝对值的一个源码,也就符号被为零,然后后边幺幺零幺。mq里边存放的是乘数,这是最开始的一个状态,那乘数是y的绝对值的源码,也就是零幺零幺幺。然后最后acc里边存放的是沉积的高位,什么叫沉积的高位,什么叫沉积的低位,这个大家一会儿会有呃切实的体会。
在这儿,大家只需要知道,在乘法运算进行之前,我们需要把ACC这个寄存器给清零。好,现在对比手算乘法,我们刚开始是不是计算乘数的最低位和被乘数的一个位积对吧?而现在乘数存在mq里边,我们在这儿把最低位的颜色涂成了更深的灰色,所以此时更深的灰色这一位就是当前要参与运算的一个位。好,那规则是这样的,如果当前参与运算的这个v=1,那么我们需要让ACC的值,加上被乘数。
而如果当前的这个位为零,那么ACC要加上零也就是什么也不加。好,目前来看,现在要参与运算的这个位是一,所以我们让ACC里边的值和被乘数进行一个相加。那这个过程是由alu,也就是算术逻辑单元里边的加法电路来完成的。那这两个数相加之后的结果是零幺幺零幺。相加的结果会被放到ACC寄存器里边好,那这相当于我们把第一个位积给算出来了。那接下来我们要计算第二个位基的时候,由于第二个位基和第一个位基进行相加的时候,需要有一个错位。所以计算机的处理方式是让ACC和mq里边的这些呃数据统一的逻辑右移。
一位所有的这些位都向右移那ACC的最低位会移到mq的最高位这个位置。变成这个样子。那由于是逻辑右移,所以我们在高位补的是零。好,那这就相当于我们在接下来进行加法操作的时候,是让下一个位积和之前得到的位积进行了一个错位的相加。另外,这个地方大家会发现mq里边之前的最低位已经被我们移出了寄存器,其实就是直接丢弃了。因为乘数的最低位之前已经用过了,之后肯定用不到,所以我们可以直接把它丢弃好,接下来我们要计算的是次低位和这个被乘数的一个位积。那么,
由于之前的右移,这个乘数的次低位此时来到了mq的最低,这个位置,因此接下来同样的我们是用mq的最后这一位。进行位积的运算,那由于这个位也是一,因此接下来我们会让ACC的内容加上被乘数。也就是刚才我们画框的这个加法,那这个加法得到的结果应该是幺零零幺幺,这个大家可以自己具体算一下。所以这次的加法导致ACC的结果更新为这样的一个数。好,那下一个位积的处理方式是不是也是类似的?为了让下一个位积和之前得到的这个部分进行一个错位的相加,所以我们会让ACC和mq统一的右移一位。
再次强调是逻辑右移,所以我们高位要补零。对了,这儿补充一个概念,就是我们呃红色的这个部分,我们可以把它称为部分肌。好继续。由于当前参与运算的这个位,它的值是零,所以这一次我们让ACC的值当前位加零就可以。那ACC+0之后,它的值是不会变的呃,每一次加法之后,我们都会进行一次逻辑右移,所以接下来右移的结果是这样的。
好,接下来要参与运算的这个v,它的值是一,所以我们让ACC加上被乘数x。也就加上最后的这一项,所以ACC的结果更新为这个值。做完加法之后,再进行逻辑右移,得到这样的一个结果,好此时mq的最后一位。是零,但是这个零我们不需要让它参与这个位积的运算,因为这个零它其实是原本的陈述的一个符号位。所以在数值位有n位的情况下,我们只需要重复n次加法和一位,
就可以得到最终正确的结果,那定点小数它的小数点是隐含在符号位后面的,这个位置的。所以x和y的绝对值相乘的结果就是零点幺零零零幺幺幺。和我们手算得到的结果是一致的。那结合之前手算的这个过程,相信大家能够理解,为什么每一次要进行一个加法,每一次要进行一个移位?那这个原理,希望大家能够好好体会,只要能够理解这个原理,那你肯定也能记住做题的方法。好的,那么经过之前这一堆骚操作,
我们得到的其实是x和y的绝对值的一个乘积,别忘了我们还需要处理符号位,所以最后我们还需要根据x和y的符号位。异或的一个结果来代替修改这个符号位,那由于一和零异或是等于一,所以我们把这个符号位修改为一,这样的话我们就得到了x和y。用原码的移位乘法得到的一个值。那这个值同样是源码的表示。那现在大家再来体会,为什么说ACC里边存储的是沉积的高位,而mq里边最终会存储沉积的低位?现在就应该能够理解这两个名词背后的含义了,另外为什么要叫源码的移位乘法呢?因为我们每一次参与运算的都只有一个位。
所以叫源码的一位乘法,那其实还有更快的一种乘法,实现的方法就是源码的二位乘法,每一次有两个位来参与运算。那这个地方就不拓展了,大家重点要理解的是这个源码移位乘法好的,那么刚才我们是用机器的方式来一步一步模拟。接下来我们来看一下,如果我们手动做题手算的话,应该如何来描述这些乘法执行的规则?这个部分我是直接从王道书上呃截下来的。要计算x和y的乘积,那么数值部分我们是通过绝对值相乘来进行运算的,不过这地方大家会发现我们课本里边就是被乘数和乘数。它是用这种双符号位的形式来进行描述的,
但是事实上我们使用单符号位也不会出错。在唐硕飞版的教材里边,它就是采用单符号位的一个描述方式,反正大家做题的时候用双符号位单符号位都可以。不过,由于补码的乘法呃一定要使用双符号位,所以为了方便大家记忆,大家在做这个源码乘法的时候,也可以把这个被乘数写成这种双符号位的形式。保持和补码的一个统一好,那通过之前的讲解,我们知道乘数只有数值位会参与运算符号位是不参与运算的,所以在我们手算的时候,其实我们只需要写出乘。陈述的这数值部分就可以了。
另外,我们知道被乘数的这个值是存到了通用寄存器x当中,然后乘数刚开始是存到了mq乘商寄存器里边。高位的部分机,我们是用ACC来记录,刚开始需要初始化为零。好,那这个地方我们画了小横线的这一位,就是当前要参与运算的这一位,也就是这儿所谓的c4。当c4=1的时候,需要让ACC里的值加上x,也就是被乘数的绝对值当c4=0的时候,是要加上零。每一次加法运算得到一个结果之后,
一定需要进行一次逻辑右移。由于是逻辑右移,所以呃高位一定都是补零,另外这个右移会导致mq也就城商寄存器里边呃之前的最低位被丢弃。好,那在数值位有n位的情况下,只要经过n轮的加法和一位。就可以得到绝对值相乘的一个结果,然后最后我们再通过符号位的一个异或运算,可以确定符号位应该为多少?那这地方课本上是写了x×y的一个用二进制表示的真值。那如果大家自己答题的话,最好还是在写出这个真值,它所对应的源码机器数。好的,
总之对源码进行移位乘法,无非就是进行n轮的加法和移位。每一次加法之后,一定都需要向右啊,并且是逻辑右移。一位只要重复n轮,就可以得到最终的结果。而至于每一步的加法需要加什么,这个需要看当前mq的最低位,它如果为一的话,那么就是ACC+x的绝对值的一个源码。如果mq当前最低位为零,那么就是ACC+0也就什么也不加,直接右移就OK。好的,
那以上就是源码乘法的一个实现方式啊,对了,这个小节当中我们是使用小数的源码乘法作为例子。其实两个整数的呃乘法实现也是类似的,大家只需要把这个小数点改成逗号就可以。另外两个小数相乘,最终得到的结果,这小数点是固定在符号位后边这个位置,而如果大家算的是两个整数的乘法,那么最终小数点应该是固定在这个位置才对。所以整数的运算和小数的运算很类似,这儿就不再赘述好的,那大家可以再结合这个PPT再来消化一下。