2.2.2 定点数的移位运算
00:00
这一小节中我们来学习定点数的移位运算怎么实现。移位运算又可以进一步的划分为算术移位、逻辑移位还有循环移位。我们会按从上至下的顺序依次讲解。
00:13
好,首先来认识一下什么叫做算术移位。我们从大家熟悉的十进制数出发,假设这儿有这样的一个十进制数985.211,那么我们从小经常做的一个事情是让小数点后移一位或者后移两位,那小数点每后移一位相当于我们对整个数值乘以了一个十的1次方,也就是奇数的1次方,而小数点后移两位相当于乘以10的2次方,小数点往前移也是类似的一个效果,只不过往前移的话,那相当于是除以10的1次方和除以10的2次方。
00:56
如果结合我们之前对二进制数它的实际数值的一个定义来看的话,其实我们移动了小数点之后,相当于改变了每一个数码位的位权,因为每一个位的位权为多少,其实是以小数点的位置作为参考的,所以所谓的算术移位,算术移位的意思就是我们通过改变各个数码位和小数点的这种相对位置,从而改变各个数码位的位权。我们可以用这种算术移位的方式来等价的实现乘法和除法,这是大家熟悉的十进制。
01:39
对于我们之前的小节中学习的二进制数,其实也是一样的。对于定点数来说,我们没办法改变小数点的位置,但是山不转可以让水转,所以我们如果能够移动这个数值部分,只要能改变每一个数值位和小数点的相对位位置关系,那我们同样可以实现算术移位的运算。比如这儿我们已经有了-20这个数的原码表示,好,来看一下进行了算术右移,右移了一位之后,得到的这个值应该是二的1次方,再加上二的3次方,也就是等于10,那么在考虑上这个符号位就应该是负十这样的一个值,所以和我们之前十进制推出的结论是类似的。
02:26
当我们对二进制的这种定点数右移一位之后,相当于我们实现了除以基数的1次方这样的一个操作。结合这个图并不难理解,本来以前这两个一,它们的权重分别是二的4次方和2的2次方,右移一位之后,它们的权重都分别除以了2,一个变成了二的3次方,一个变成了二的1次方,整体都是这样缩小了一半。所以这就是算术右移的一个效果,相当于除以2。
03:01
好,我们再右移一位。那么刚才这个最低位就会移动到小数点的后面这个位置,由于我们的机器字长有限,所以移出去的这一位我们就只能舍弃不用,同样的新的这个高位我们会用零来补充,这次右移的结果同样也是相当于再除以一个二。
03:21
好,继续在这个基础上我们再右移一位。这个时候我们会把末位的一给移出,这八个比特的范围同样高位补0,不过这个时候得到的值,它应该是-2,这样的一个值已经不是-5除以2的精确表示了,因为-5除以2应该是负的2.5,这样才是精确的除以二的值,那这是因为我们移出去的这一位,它的值不是0而是1,所以其实相当于我们舍弃了二的负1次方这样的一个精度。因此我们得到这样的结论,当我们进行算术右移的时候,首先高位会用零来补充,然后低位直接舍弃。如果我们舍弃的这一位是0的话,那么就相当于严谨的除以2的一个结果,而如果我们舍弃的这一位不等于0,那在这种情况下我们会丢失一定的精度。好,这是算术右移。
04:24
接下来来看算术左移,左移的方式也是一样的,我们只让这个数值部分进行移动,符号位是保持不变的那进行左移之后,原本数值位的最高位会被我们舍弃,而最低位出现的这个空位我们会用0来代替。进行这样的一次右移之后,得到的结果应该是负的40,相当于在原有的基础上乘以2。如果再左移一位也是类似的效果,我们丢弃最高位的0,然后相当于在原有的基础上再乘以一个2。好,我们再左移一位。由于这次我们丢掉的最高位它是1,所以最终我们得到的结果这等于负的32。
05:11
这次左移操作就不是乘以2这么简单了,这一点其实也很好理解,因为我们原本左移两位的时候,这个数值就已经到了负的80这样的一个值,那负的80如果让它再乘以2,应该是负的160这么多,而这个地方我们原码的尾数只有七位,七个比特位,只能表示0到127这样的一个绝对位置的范围,所以160肯定是已经超出七个比特位能够表示的范围,那这种情况下我们丢弃最高位的一权值最高的那个一被我们丢弃了,当然就会出现这种严重的误差。好,这是对原码的算术左移需要注意的地方。
05:58
好的,目前为止我们探讨的算术左移和算术右移是基于用原码表示的定点整数来探讨的,那如果不是定点整数,而是定点小数,其实也是一样的道理,一样的效果。当我们进行算术左移的时候,同样相当于乘以2的1个效果,算术右移相当于除以2的一个效果。因为所有的这些一,它们的位权在左移和右移的时候分别会乘以2和除以2。好,所以定点小数我们就不再单独的探讨。
06:34
好,那接下来我们再来看基于反码的算术移位,这儿我们已经给出了正20和-20的1个原码表示。由于正数的反码表示和原码表示是一模一样的,所以对于正数的算术移位,不管是左移还是右移,处理的方法都是和原码一样。这是反码表示的正数需要采取的一个策略,和原码是一样的。
07:01
好,再来看负数,也就是符号位为1的数。这种数的反码尾数部分和原码是完全相反的,1变成0,0会变成1,反码的1相当于原码的0,反码的0相当于原码的1。所以对负数的算术移位,我们进行补位的时候需要注意,我们补的都是一。这是对于反码的算术移位,正数和负数我们需要补充的这个位是不一样的。
07:32
好,接下来我们再来看补码的算术移位。由于正数的补码表示和原码也是一样的,所以对于正数补码的移位运算,我们同样和原码保持一样的策略就可以,都是需要用0来补充移动之后出现的空位,而负数补码的移位会相对来说复杂一些。
07:54
补码是从反码的基础上末尾加一得到的,那反码的末位加一会导致反码当中更靠后的这些一都会变成0,并且都会发生进位,直到进到第一个零这个地方为止。所以反码转补码有这样的一个规律,就是从反码的最右边这一位开始,从右往左依次的取反,把1都变成0,直到碰到第一个0为止,把反码的第一个0变成1之后,再往前的这些部分就不用再改变了。所以负数的补码呈现出了这样的一个规律。
08:37
在这个补码最右边的一个一,还有这个一再往右的这些部分,这些部分是和原码保持一致,而最右边的这个一,左边的这些部分又是和反码保持一致的。所以当我们对补码的这些尾数进行算术右移的时候,往右移会导致高位出现一个空位。那我们补这个空位的方法应该是和反码的补空位规则是保持一致的,也就是补一。
09:14
而当我们对补码进行算术左移的时候,最低位会出现一个需要补的空位。由于补码的后半部分和原码是相同的,所以我们在补这个空位的时候应该补0。因此我们得到结论:对于负数补码的算术移位,当右移的时候我们应该补1,然后低位舍弃。当进行算术左移的时候,应该低位补0,然后高位舍弃。那用这样的方式我们就可以保证对补码的算术右移,同样是相当于除以2的一个效果,算数左移相当于乘以2的一个效果。
09:55
好的,那我们再对算术移位进行一个小小的总结:对于正数来说,由于原码、补码、反码,它们的正数表示都是一样的,所以我们需要补位的时候都是用0去补。而对于负数来说,补码的负数左移的时候需要添0,右移的时候需要添1,而反码的负数,不管左移还是右移,我们都是要添1。只要遵从这儿给出的规定,无论我们使用的是什么码,只要我们进行的是算术左移就相当于乘2,只要是右移就相当于除以2,只不过由于我们机器字长位数有限,所以有的时候我们没办法用算术移位精确的来等效乘除法,这一点我们在讲原码的左移和右移的时候特别的强调过。
10:46
好,接下来用一个简单的例子让大家体会一下算术移位的一个具体的应用。我们之前已经探讨了-20这个原码,它左移一位和左移两位所得到的一个结果。好,如果说现在我们要算的是-20乘以7,要让计算机完成这个乘法运算。那7这个数我们可以把它拆分为二的0次方,加2的1次方,加2的2次方。所以-20乘以7我们可以把它拆解为这样的三个乘法。
11:18
硬件在执行乘以七这个过程的时候,其实就相当于把-20这原有的数的基础上不移位,还有左移一位,左移两位,这样的三个数进行一个相加的操作,就可以等效的完成-20乘以7的操作。所以计算机硬件实现乘法其实是基于算术移位还有加法来进行的,而实现算术移位的硬件电路设计起来并不复杂,具体乘法怎么实现,我们还会用之后的小节进行更进一步的探讨。
11:54
接下来我们再来看第二种移位,叫做逻辑移位。逻辑移位的规则很简单,当我们右移的时候高位补0,然后低位移出的这一位直接舍弃就可以,左移的时候我们在低位补0,然后移出的这一位我们直接舍弃就可以。逻辑移位的这种规则,我们可以把它看作是对无符号数的算术移位。
12:22
好,这个规则很简单,我们来看一下逻辑移位有什么作用。比如说我们在计算机里面表示一种颜色的时候,经常会使用到这样的一种表示方式,叫做RGB。R指的是red也就是红色,G表示的是Green也就是绿色,而B表示的是blue也就是蓝色。因为我们知道自然界里边所有的颜色都是由红绿蓝这三种三原色来按照一定的配比来组成的。比如对于最后这个颜色,它的RGB值就分别是102、139和139。
13:05
有的时候我们存储一个像素点它的RGB值的时候,需要把RGB这三个值把它们连成三个字节的一个整体。第一个字节存放R,第二个字节存放G,第三个字节存放B的值。而现在我们只是分开指明了这三个部分的值分别是多少。好,来看一下怎么把它们拼成三个字节的一个整体。可以这么做,首先我们申请用三个字节来存储无符号数102,也就是R的值,那现在我们对这个无符号数进行逻辑左移,左移16位,这就会导致低八位移动到高八位的位置,然后左移产生的这些空位我们都是用零来补充,这是逻辑左移的一个规定。
13:57
好,接下来我们再定义一个三个字节的无符号数,这个数的值是139。那么我们再对这个无符号数进行逻辑左移八位的操作,就会导致原本的第八位被放到了中间的八个位的位置。好,最后我们再用三个字节来存储无符号数,也就是B的值139。好,最后我们再把刚才得到的123这3个部分进行一个加法操作,就可以得到三个字节表示的RGB值,最高的一个字节表示的是R值,中间表示的是G也就是绿色的值,然后最后表示的是blue的值。好,这是逻辑移位的一个应用的小例子。
14:45
接下来我们再来看最后一种移位运算,叫做循环移位,顾名思义,所谓循环就是指当我们进行比如说循环左移的时候,移出来的这一位会被放到我们需要填补的这个空位。在移位的时候整个二进制串是进行循环补位的,这个应该很好理解。那当我们进行循环右移的时候,是不是也是类似的,从右边移出来的这一位又会跑到应该补充的那个位置去。好,还有一种比较特殊的循环移位,就是带进位位的这种情况。
15:23
先来解释一下什么叫进位位,也就是儿我们标注的CF这一位。进位的概念大家都知道了,比如我们对两个八位的二进制数进行加法操作。当我们运算到最高位的时候,1加1是等于0,往高位进1。但是由于机器字长有限,寄存器里只能保存八个二进制位,而最高位又确实产生了一这样的一个进位。因此为了实现超过8比特的这种数据的加法,计算机硬件里边会包含这样的所谓的进位的位,来记录一下之前这些低位的运算有没有产生进位。把这个进位保留下来之后,我们再进行之后的更高字节的运算的时候,就可以得到正确的结果。
16:14
0加1再加上刚才保留的这个进位1,然后用这样的方式我们就可以不断的往高位进行计算。好,所以这就是所谓的进位位的一个作用。总之它里面要么存了一个1,要么存了一个0。
16:30
好,现在当我们考虑上这个进位之后,再进行循环左移,产生的效果就是这样的。就是会把原本数值位的最高位把它放到这个进位位这个地方,而原本的这个进位位会来补充出现了空位这个样子。好,这就是带进位位的循环左移。
16:56
那带进位位的循环右移是不是也是类似的?无非就是把末尾的这个低位放到进位位的位置,而原本的进位位把它放到最高位出现空缺的这个位置。好,这就是所谓的循环移位。
这个小节中我们学习了定点数的移位运算,其中最常考的是算术移位,当我们进行算术左移移位的时候,相当于乘了一个基数。当我们算术右移移位的时候,相当于除以基数的效果。
17:29
原码、反码、补码进行移位之后,补位的这个策略是不太一样的,特别是补码大家比较容易忘,需要基于理解来记忆。逻辑移位的实现很简单,无论是左移还是右移都补0就可以。之后我们又学习了循环移位,就是用移出去的位来补上空缺的那个位,而对于待进位位的循环移位就是移出的位,我们会把它放到进位位的位置,而原来的进位位会补上空缺的那个位置。
18:01
再次强调由于原码、补码、反码它们的位数有限,也就是可以表示的数值范围是有限的。所以在某些情况下,移位操作并不能精确的等效乘法和除法的一个效果,可能会丧失精度,甚至是产生比较大的误差。好的,这就是移位操作相关的所有内容。
标签:右移,算术,左移,原码,定点数,2.2,我们,移位 From: https://blog.csdn.net/m0_51768219/article/details/141502777