@[toc]
一、介绍
之前的小结中我们探讨了定点数的原码和补码的乘法如何实现。
这个小结,我们要来探讨定点数的除法运算如何实现。
首先我们会介绍除法运算的思想,接下来会介绍原码的两种除法实现方法,一种叫恢复余数法,一种叫不恢复余数法,又叫加减交替法。然后,我们又会介绍补码的除法,同样也是采用加减交替法的思想。
二、原码的除法运算
(1)手算除法之十进制
1.过程
首先来看一下大家熟悉的十进制
除法, 0. 211/ 0. 985。
小时候我们做这样的除法,首先是会把这两个数的小数点进行一个处理,统一向右移三位,小数点右移三位就会变成 211 除以985。如下:
除法大家都很熟悉了。
<1> 第一个商我们只能上0,因为我们上的这一位商(0)和除数(985)进行相乘之后,得到的结果(000)不能大于当前的被除数(211)。
这样我们可以得到一个余数211,并且在余数的后面添一个0。
<2> 接下来第二步,我们可以商 2,因为2 乘以985,刚好是不大于2110,但是又最接近 2110 的一个数值1970。
所以得到1970,再和之前的余数(2110)进行相减,得到三位的余数(140)。接下来再在末位添一个0。
后续的都一样。总之,每一步我们上的这一位商和除数的相乘的乘积要尽可能的接近当前的余数,但是又不超过当前这个余数。
如果我们最终追求的商只精确到小数点,后面三位,最终会留下一个余数210。如下:
2.原理
现在问题是这样的,大家有没有想过,为什么我们小时候学的这种除法的竖式,要这么计算,其实这背后的原理和上一小节的乘法是类似的。
我们可以从 r 进制数的数值定义来出发,可以把原本的 0. 211 除以 0. 985,写成这样的一个形式。
所谓的除法,用被除数x除以除数y,得到一个商a,最终剩余的余数是b。
这背后的含义是被除数 x 可以等于a,也就是商乘以除数y,再加上余数b。
所以我们上边除法运算的结果,我们可以把它解释为 0. 211 也就是被除数,等于除数 0. 985 乘以商 0. 214,再加上最后的余数 0. 000210。大家可以验证一下这个等式是成立的。
我们把最终得到的商按照位权展开,写成 r 进制的定义的形式。
2 乘以 10 的负1次方就是 0. 2;1乘以 10 的负2次方就是 0. 01;4 乘以 10 的负3次方就是 0. 004。所以 0. 214 这个数可以把它看作 0. 2 + 0. 01 + 0. 004。
<1> 第一步
现在我们把除数和商进行相乘。也就是 985 和刚才展开的每一项进行一个组合,第一项的组合就相当于 0. 985 乘以 0. 2,这个乘法得到的结果是 0. 1970。
而我们之前这一步得到的余数(0.2110),也就是剩余的我们还需要拼凑的数(0.2110)再减掉这一步拼凑上的 0. 1970 等于 0. 01400,也就新的余数,是我们接下来还剩余的还需要继续拼凑的部分。
<2> 第二步
接下来我们再拼凑上去的部分就是 0. 985 乘以 0. 01,也就是和这一位的商(0.214)的1进行相乘,得到的结果是 0. 00985。
这一次拼凑(0.00985)和之前剩余的部分(0.01400)进行相减,又可以得到一个新的余数0.004150。于是下一位的商 0. 004 乘以 0. 985,就是想要尽可能的拼凑出此时还剩余的部分(0.004150)。如下:
所以所谓的余数
就是目前还剩余的,还需要拼凑的部分。
而每一次我们上一个商
,其实就是想要尽可能的接近当前剩余的余数,但是又不超过这个余数。
这就是小学学习的除法竖式为什么要这样错位相减的原因。
(2)手算除法之二进制
1.过程
接下来我们尝试着把十进制除法的这种运算思想,把它迁移到二进制
。
来看一个比较简单的例子,现在有 x 和 y 这样的两个二进制数,并且这两个二进制数都是正数。
我们可以把除数和被除数分别乘以 2 的 4 次方,也就相当于分别把小数点都往后移,4位也就变成了两个整数的相除。如下:
现在我们模仿十进制除法的上商规则。
<1> 第一步
刚开始被除数(01011)有 5 位,除数(01101)有5位。
由于此时的被除数小于除数,而我们上的商只能是 0 或者1,并且这一次上的这一位商和除数相乘的乘积不能大于此时剩余的我们需要拼凑的部分(01011)。
所以由于刚开始被除数是要小于除数的,因此我们只能上0。0乘以除数(01101)等于 00000。
然后我们就可以得到一个四位的余数(1011),因为高位是0,所以我们可以把它忽略。如下:
得到四位的余数之后,在末位补一个0。如上图。
<2> 第二步
接下来10110这个余数要大于除数(01101),所以下一位的商我们可以上1,1乘以除数(01101),得到的结果就是除数本身(01101)。
之前的余数(10110)减掉除数(01101),可以得到一个4位的余数(1001)。
接下来我们再在4位余数后面再添一个0,就变成了10010。
<3> 第三步
接下来是类似的,此时剩余的余数(10010)要比我们的除数(01101)更大,所以这一次我们同样可以上商1。
接下来,用余数(10010)减掉除数(01101),可以得到一个四位的余数(0101),同样的在末位添0就变成了01010。 如下:
<4> 第四步
现在我们剩余的余数 01010 要比除数(01101)更小,而我们接下来上的这一位商和除数(01101)相乘之后,不能超过余数(01010)的值,所以我们接下来只能上商0。
因此这儿得到的是 00000。
01010和00000相减,得到4位余数1010,再添一个0,得到10100。
<5> 第五步
最后我们上商1,最终相减,得到一个4位的余数。
这是我们模仿十进制小数的除法得到的一个结果。
2.原理
和之前类似,如果我们把每一位商的位权给考虑进去,那么我们得到的二进制竖式其实也可以写成这个样子。如下:
比如第二位的商(1)相当于二进制的 0. 01乘以除数 0. 1101。二进制的 0. 01 等于 1 乘以 2 的负2次方,所以除数(0.1101)乘以这一位的商(0.01)应该是在除数的基础上,小数点再往前移两位,也就是 0. 001101。如下:
所以这一位的商和除数相乘之后,我们得到的一个乘积结果就是 0. 001101。如下:
其他位的商也是类似的原理。
大家可以自己验证一下。用商(0.1101)乘以除数(0.1101),再加上余数(0.00000111),是否可以拼凑出被除数。
我们来简单总结一下手算二进制除法的规律。
首先,由于定点数它的小数点都是固定的,所以当我们在进行两个正的定点小数的除法运算的时候,其实可以忽略小数点,就像左边这个样子。如下:
我们每一次会确定一位商,并且在二进制除法当中,每一位商只有可能为 0 或者为1。
每一位的商到底取 0 还是取1,我们是通过新算的方式得到的。我们会观察当前剩余的还需要拼凑的部分和除数的大小到底谁更大。
①如果除数要更大,那么这一次我们就商0。
②而如果此时剩余的余数要比除数更大,那这一次我们就会商1。 如下:
当我们在确定了一位商之后,就可以得到一个新的余数,并且基于我们上商的原则,我们新得到的余数最高位一定是0,所以我们可以把最高位的 0 给忽略掉,只关注余数后面的4位。
接下来我们再在余数的末尾再添一个0,于是就可以再开始确定下一位的商。
由于机器字长是 5 位,所以当我们确定了第五位的商之后,最后得到一个余数,我们就不需要再往后继续了。
(3)机器实现
接下来我们要思考的问题是如何把这种手算的思想尝试着用机器
来实现。
当我们在讲到运算器基本组成的时候,我们提到过,如果此时进行的是除法运算,那么ACC寄存器会用于存储被除数,还有余数。MQ会用于存储商,而X通用寄存器会用于存储除数。
接下来我们来看一下运算器如何实现原码的除法。
1.恢复余数法
接下来介绍这种方法叫恢复余数法
。
原理
之前我们探讨的手算除法是计算两个正数的除法。对于原码的除法来说,被除数和除数既有可能是正的,也有可能是负的。
类似于原码的乘法,最终乘积的正负性我们会单独的用一个异或运算来确定。
只需要把被除数和除数的符号位进行一个异或就可以确定最终的商应该正是负,而商的实际数值我们会用被除数和除数的绝对值进行除法计算,这样我们就可以转换成之前探讨的两个正数相除的形式。
现在我们先忽略符号位的处理。
x 除以y,我们先写出 x 和 y 的绝对值的原码表示。需要写出除数的绝对值的补码,还有除数的绝对值再取负的一个补码。写出它相反数的补码有什么用,大家一会知道。
刚才我们说ACC里面会存储被除数,也就是 x 的值01011;通用寄存器里边会存储除数的值,也就是 y的值 01101 ;MQ 寄存器里边会存储最终的商,刚开始我们需要把它全部置为0。如下:
大家会发现 MQ 最后的这一位,我们把它涂成了比较特殊的深灰色。这一位其实就是当前要确定的一位商。这一位的作用大家一会体会到,现在先不管。
现在回头来看一下之前手动求除法的一个过程。
之前在我们手算的时候,我们每一位的商到底取 0 还是取1,是通过当前剩余的部分,就是余数和除数的大小关系来确定的。
我们用心算的方式来确定到底应该商 0 还是商1。
对于运算器来说,其实就是要判断此时 ACC 里边保存的数和通用寄存器里边保存的除数到底谁更大。
如果 ACC 里边保存的更大,就应该商1,如果 ACC 里边保存的数更小,就应该商0。
这是一个比较理想的处理方式。但事实上我们的计算机比较傻,它并不会比较这两个数谁更大谁更小,没有这样的功能。
计算机确定它该上商 1 还是上商 0 的方式是这样的,它会先默认这一次要上商1,如果这一次上商上的是1,就意味着我们需要把ACC 当中此时存储的数和通用寄存器当中存储的除数进行一个相减的操作,再把相减的结果放到 ACC 里边。
因为每一次我们上商 1 其实就是用余数减掉除数。要实现余数减掉除数,就相当于要让余数加上除数的负值的补码,因为计算机里边并没有减法电路,所有的减法都是用补码的加法来等价的实现的,所以这就是为什么我们之前要写出除数的负值的补码的原因。
过程
<1> 第一步
来看一下,此时我们要确定的是低位商,我们用手算的方式知道,本来是应该上 0 的,但是计算机不一样,它会默认此时应该上1。
基于之前的推论,如果这一次商要上1,就需要用 ACC 里边保留的被除数,或者也可以把它看作是当前的余数减掉除数的值,也要加上除数的负值的补码。
然后再把减法操作的结果放回 ACC 累加寄存器当中。
这一次余数减除数得到的结果是1110。
加法是通过ALU完成的, ACC里面的值送到ALU,通用寄存器里的值也送到ALU。ALU 里边的加法器做完加法之后,会把这些加的结果再次送回ACC,把 ACC里边的内容给覆盖,所以经过这一步的操作, ACC里的值会更新为 1110。如下图:
现在问题来了,之前我们求余数的这一步减法操作,得到的相减的结果是一个负数。就说明之前的余数是要比除数更小,所以之前上商上 1 其实是不应该的。
于是当运算器检测到符号位为 1 的时候,他就知道之前上商 1 上错了。
于是怎么办,知错就改,应该把商从 1 改为0。
既然现在应该商0,而不是商1,所以我们之前用余数减掉除数所得到的这个值是不是错误的?本来不应该减掉除数,而应该是减掉全0,也就是什么都不减。
所以我们必须把 ACC里边的数值恢复原样,恢复成原有的余数。
之前我们是减了一个除数,我们要把它恢复原样,那就再加上除数,把它加回去就行了。
所以基于之前相减的结果,在这个结果的基础上,运算器会再加上除数,把这两个数相加的结果再次的送回ACC,把 ACC 的内容给覆盖,这样 ACC 里边就可以恢复成原本该有的余数,也就是商0得到的余数。如下:
所以这就是为什么这种方法叫恢复余数法
。
计算机刚开始会默认商1,而商 1 的时候得到的余数有可能是错误的。如果发现商错了,改成商0的话,需要把余数恢复原样才可以进行接下来的运算。
<2> 第二步
现在我们恢复了余数之后,相当于已经完成了商0并且相减的工作。
这一次相减得到的结果应该是 01011 这样的五位余数。
接下来当我们在确定下一位的商的时候,这一位的商乘以除数,所得到的结果要和之前余数进行一个错位的相减。
我们需要把首部的 0 给去掉,在末位补上一个0。
用硬件怎么实现?很简单,只需要把 ACC 和 MQ 里的内容全部统一的逻辑左移1位就行了。
也就是把 MQ 的最高位左移到 ACC 的末位, ACC 里边每一位都往前移,原本的最高位的 0 会被丢弃。如下:
由于是逻辑左移,所以 MQ 里边空出的低位我们会用 0 来补上。 如下:
<3> 第三步
现在我们已经模拟出了手算得到第二个余数的这样的步骤,接下来我们就可以确定下一位的商应该商多少。
和之前一样,计算机很傻,它会默认先商1,那商 1 就意味着此时的余数应该要减掉除数,逻辑和之前一样,减掉除数就相当于加上除数的负值的补码。
这一次相减操作得到的结果是01001,我们把相减的结果更新到 ACC 里边。
接下来运算器检查此时 ACC里边得到的这个余数,它的值是正的。
这就意味着之前这个余数确实是要比除数更大的,所以此时这一步我们商 1 并没有错。
这种情况下,我们得到的余数就是一个正确的余数(01001),我们不需要再把它恢复原样。
接下来的处理和之前类似,手算的时候,我们会在余数的末位补一个0。如下:
用硬件实现,就是让ACC和 MQ里的这些数位全部的统一逻辑左移1位。
ACC的高位 0 会被丢弃,末位会补上一个0。
<4> 第四步
现在余数我们已经搞定了,接下来我们就可以确定下一步的商应该是多少。
同样的会首先默认商1,让 ACC 的值减掉除数的值,也就是加上除数的负数的补码。
得到的结果是00101。
我们把这个结果更新到 ACC 里边,经过检查发现这一次得到的余数同样是一个正数,那就说明之前商 1 是没有错的。
因此进入下一步,让 ACC和 MQ全部逻辑左移,MQ的低位补0。
<5> 第五步
现在我们已经确定了余数的值是多少,接下来就可以确定下一位的商应该商多少。
同样的,计算机很傻,它会默认先商1,让ACC里的值直接减掉除数,也就是加上除数绝对值的负值的补码,得到的结果是11101。
这一次我们得到的余数是一个负值,说明之前这一步的余数要比除数更小,所以才导致了减法之后得到一个负值。
既然余数比除数更小,所以之前这一步我们不应该商1,而应该商0。
所以接下来运算器会把当前这一位的商从 1 改为0,并且还需要恢复余数。
恢复余数的办法就是用当前 ACC 里的这个值再加上除数,因此余数会被恢复到 01010 这样的状态。
<6> 第六步
接下来应该进入下一步,让余数还有商统一的左移,逻辑左移一位,低位补0,就得到了下一步我们需要处理的余数10100。
此时我们需要确定最后一位的商是多少。
同样的计算机会默认商1,让ACC的值减掉除数的值,也加上这除数的负值的补码,得到的结果是00111。
对应手算我们最后这一步得到的余数的结果。如下:
由于我们这是假定机器字长只有 5 位,因此我们只能求 5 位的商。
到这一步我们就求出了最终的商。
需要提醒大家的是,最后这一步我们上商 1 得到了一个正的余数,所以到这一步我们除法就结束了。
但是如果我们最后这一步上商1,最后发现余数为一个负数,那我们还是需要恢复余数,也还需要在当前这一步的基础上,让 ACC 的值加上除数,并且当前这一位的商,我们需要把它从 1 改成0,这样才能结束。
最后这一步的余数正是负,会直接影响到我们的除法应该在哪一步停止。
由于我们这儿假定的是一个定点小数的除法运算,所以小数点隐含在符号位的后面。
商的值是 0. 1101,和我们手算的结果一致。而最终得到的这个余数需要在 0. 0111 的基础上再乘以 2 的负 n 次方, n 的值是4,所以真实的余数需要在这个基础上再乘以 2 的- 4 次方。
最后我们还需要关注符号位
应该是多少。
这个例子当中,由于 x 和 y 都是正的, 0 和0异或等于0,所以这个符号位为0,商保持正值不变。
计算机会默认上商1,而如果发现余数得到了一个负值,我们就需要把商从 1 恢复成0,同时把余数恢复原样。
手算模拟
这儿给大家画出硬件细节,是为了让大家理解得更深。
如果用手算的方式模拟恢复余数法,用这样的方式写出来就可以了。如下:
来看一下,题目会说明机器字长是多少位,并且给出被除数和除数的值。
我们需要给出被除数和除数的绝对值的原码表示,并且写出除数的绝对值还有绝对值取负的补码的形式。
<1> 第一步
刚开始我们会尝试着用最初始的被除数减掉除数,也加上除数的绝对值的负值的补码。
用这样的方式得到一个余数,此时这个余数是一个负值,符号位为1。
所以我们可以确定当前这一位的商我们应该商0,并且在负的余数的基础上再加上除数来恢复余数,恢复成它原本该有的样子。
然后把这个余数逻辑左移,就意味着低位一定补0。
<2> 第二步
所以恢复了余数并且逻辑左移之后,我们就可以开始确定下一位的商应该商多少。
同样的,我们会先减掉除数,用这一次减法操作得到的余数的正负性来确定当前这一位的商,我们应该取 0 还是取1。
如果余数为正,那就确定这一位商应该取1;如果余数为负,那么就确定这一位的商应该取0。
由于我们此时得到的余数为正,不需要再恢复余数,可以直接把当前这余数给逻辑左移,末位添0。
这样我们又可以进入下一位的商的确定。
<3> 第三步
同样的,要减掉除数,根据余数的正负性来确定商到底取 1 还是取0。
后续的就不再展开。大家可以自己在稿纸上手算,模拟一遍。
总结
来进行一个小小的总结。
对于原码的恢复余数法
,我们首先要用老的余数减掉除数的绝对值,得到一个新的余数。
在最开始的时候,我们可以把被除数看作是余数。
第一步一定是减掉这个除数,我们会根据新余数的正负性来判断我们这一位的商应该商 0 还是商1。
①如果新余数为正数,符号位为0,那么我们就可以确定这一步我们应该商1,并且直接把之前得到的余数逻辑左移,接下来就可以进入下一轮的处理。
②而如果之前得到的所谓的新余数,它是一个负值,符号位为1,我们可以确定当前这一位的商应该商0,并且通过加上除数的绝对值的方法来恢复余数。再把老余数给逻辑左移1位,就可以再进入下一轮的处理。
需要注意的是,如果数值位为n,逻辑左移只需要进行 n 次,但是上商我们需要上 n 加 1 次,最后这一次得到的余数我们是不需要进行逻辑左移的,所以左移的次数会比上商的次数少一次。
这就是原码的除法恢复余数法。
2.加减交替法
恢复余数法简化
现在的问题是这样的,每一次我们都先默认商1,当我们发现余数为负的时候,再进行加除数恢复余数这样的一个操作。这种操作显然是比较麻烦的,所以接下来我们要思考的问题是能否不恢复余数,把中间的一些步骤给进行简化。
我们来分析一下恢复余数的过程。
来看这个例子。刚开始这一步减掉除数,得到了一个负的余数,我们把这余数记为a, a 是一个负值。
接下来由于 a 是负的,所以我们需要让 a 加上除数的绝对值,我们把除数的值记为b。
再往下 0. 1011,这一步其实是 a 加 b 的一个结果。
接下来我们会把 a 加 b 逻辑左移1位。这个逻辑左移相当于乘以 2 。
我们把它展开就是 2a 加 2b 这样的结果。
现在基于 2a 加 2b 这样的一个结果,我们会再减掉除数,也就是减掉 b 来得到下一步的余数。
2a 加2b,再减掉一个b,就是 2a 加b。
所以之前这一步我们得到了一个负的余数,把负的余数一步一步恢复左移,再减掉除数,得到了确定下一位商所需要的余数。
这中间的过程,我们中间做的这一系列操作,其实可以把它合并为 2a 加b,也就是余数乘以2再加上除数这样一个综合的效果。
之前余数乘以2,不就相当于把余数左移1位吗?在左移1位之后,再加上除数的绝对值,就可以直接得到下一步的余数。
这样我们恢复余数的步骤就可以直接被跳过,可以让流程简化,效率更高。
所以这就是优化恢复余数法的策略。
如果我们发现余数得到了一个负值,我们可以直接确定这一位的商应该是商0。接下来我们不需要恢复余数,而是让余数左移1位再加上除数的值,就可以直接跳到下一步。
基于这种优化思路的原码除法,我们可以把它称为加减交替法
,又可以称为不恢复余数法。
举例
来看一下,刚开始被除数是这样的一个值存放在 ACC 里边, MQ 刚开始全部为0。如下:
第一步我们一定是除数减掉除数,之后得到的余数是一个负值。
我们可以确定此时的商应该商0。
基于之前的推论,我们不需要恢复余数,可以直接让余数逻辑左移。逻辑左移之后再加上一个除数,就可以直接得到下一步的余数。
由于下一步的余数它是正的,所以我们可以确定这一位的商我们应该商1。
由于此时余数是正的,接下来的处理方式和恢复余数法是一样的。
正的余数我们直接让它逻辑左移,用左移得到的余数减掉除数。如下:
用这样的方式再确定下一步的余数,再下一步的余数同样是一个正的,所以我们可以确定这一位的商应该是1。
总之,我们会根据每一步得到的余数是正还是负来确定商应该是 0 还是1。
除了确定商的值之外,我们还会根据余数的正负性来确定下一步我们应该左移加除数还是左移减除数。
这就是原码的加减交替法。
加减交替法就是把恢复余数法当余数为负的时候,中间的这些步骤把它统一为一步更简单的操作,是对恢复余数法的一个优化。
原码的加减交替法符号位
的确定也是一样的,需要单独的来确定,用被除数和除数的符号位进行一个异或来确定商的正负性。
另外一点需要强调的是,我们最终得到的余数,它的正负性和商是相同的。如果商为负,这个余数也应该是负的值。
最后还需要注意,如果我们最后这一步得到的余数是一个负值,我们同样也需要把余数恢复原样,也就是加上除数的绝对值,用这样的方式来得到正确的余数。
所以,虽然加减交替法又称为不恢复余数法,但是在最后这一步,如果发现余数为负的时候,最后这一步同样也需要恢复余数。
当然,这只是有可能出现的情况,像刚才我们给的例子当中,最后这一步就不需要恢复余数。
总结
最后同样对加减交替法进行一个小小的总结。
刚开始第一步一定是用被除数减掉除数的绝对值得到一个新的余数。
接下来根据这个新的余数为正还是为负,来确定当前这一位的商应该商 0 还是商1。
①如果余数为负,就是商0,并且让余数逻辑左移,再加上除数,用这样的方式得到下一步的新余数。
②而如果余数为正,那么我们可以确定商1,让当前这一步得到的余数左移,并且减掉除数,用这样的方式得到下一步的新余数。
整个过程,我们加法和减法的执行次数总共会有 n 加 1 次,每进行一次加减就会更新一次余数,而每更新一次余数,就可以根据余数的正
负性来确定一位商到底是 0 还是1。
需要注意的是,当我们在确定了最后一位商之后,我们就不需要再进行逻辑左移了。
所以左移操作的次数要比加减运算的次数要少一次。
另外一点,如果最后这一步得到余数是一个负值,我们需要商0,并且再多进行一次加上除数的加法运算,用这样的方式来得到正确的余数。
所以理论上来说,加减交替法当中,加法和减法的总次数有可能是 n 加 1 次,也有可能是 n 加 2 次,但逻辑左移只需要进行 n 次。
这是原码的加减交替法,大家可以吸收一下。
号外
最后还需要跟大家聊一个问题。
在这个地方,我们探讨的是定点小数的除法运算。由于是定点小数,所以我们最终得到的商肯定也只能是一个定点小数,而不能是一个整数。
因此,在定点数的除法运算当中,我们会规定被除数一定要小于除数,因为如果被除数大于除数,最终商的结果肯定就要大于1。
而定点小数无法表示大于 1 这样的范围。
硬件又是怎么检查被除数和除数的大小关系的?
其实就是通过第一步的商来确定的。
正常情况下,我们第一步减除数所得到的余数一定要是一个负值,如果第一步得到的就是一个正值,也就是要商1,这就说明被除数要比除数更大,此时硬件电路就会检测出这个问题,并且直接停止这些除法的运算。
这种除法是没办法用定点小数来表示的。大家可以再消化一下恢复余数法和不恢复余数法它们的区别和联系。
三、补码的除法运算
刚才我们探讨了定点数原码的除法运算如何实现。
现在我们要学习补码的除法运算如何实现。
补码的除法运算和原码除法的加减交替法有很多类似的地方,所以我们会和原码的加减交替法进行一个对比的学习。
在原码的加减交替法
当中,第一步我们一定是用被除数减掉除数的绝对值得到新的余数。
接下来每一步我们会根据当前余数的正负性来确定我们应该商 0 还是商1,同时也会根据余数的正负性来确定接下来这一步我们应该进行加法还是减法,是根据余数的正负性来判断的。
另外一点,原码的除法当中符号位我们是单独来确定的,符号位不会直接参与除法运算。
最后我们会用一个异或的逻辑来判断商到底是正的还是负的。
这是原码的加减交替法。
补码的除法运算也是使用这种加减交替法的方式来进行的。
和原码的加减交替法相比,补码的加减交替法会让符号位也参与到运算当中,并且被除数、余数还有除数这些我们通常会采用双符号位的形式来表示。
这儿给出了被除数和除数的真值,我们把它转换成与之对应的补码。
注意这个地方,我们的除数是一个负数,我们写的并不是除数的绝对值的补码。如下:
这点和原码的加减交替法是不一样的,因为我们会让符号位也直接参与到运算里边。
来看一下具体的执行步骤。
<1> 第一步
在原码的加减交替法当中,第一步我们一定是用被除数减掉除数的绝对值的补码。
但是在补码的加减加替法当中,第一步我们需要根据被除数和除数它们是否同号来进行判断我们应该进行加还是减。如果是同号,我们应该进行减法;如果是异号,应该进行加法。
在这个题目当中,由于被除数和除数是异号的,所以我们需要加上除数的补码。
这一步的加法得到了一个新的余数。
接下来,每当我们得到一个余数之后,我们会根据余数和除数是否同号来确定当前这一位的商应该商 1 还是商0。
①如果余数和除数同号
,就应该商1,并且还可以确定,接下来我们应该让余数左移,做一个减法,减掉除数。
②而如果余数和除数异号
,应该商0,并且接下来应该是左移,然后进行一个加法,加上除数。
在刚才这一步当中得到的余数是一个负的,而除数也是负的。如下:
它们俩同号,所以在同号的情况下,我们可以确定当前这一位的商我们是商1。
接下来余数左移一位,并且要减掉除数。减掉除数就相当于加上除数的负值的补码。
<2> 第二步
这样我们又得到了一个新的余数,此时得到的余数是一个正值,它和除数是异号的。如下:
所以当前这一步我们可以确定这一位的商我们应该商 0。 (异号的时候应该商0)
<3> 第三步
下一步应该是左移,并且加上除数。所以接下来左移之后,我们要加上除数的补码,用这样的方式又得到了下一步的余数。
接下来逻辑是一样的,余数和除数是同号,所以同号我们可以确定当前这一位应该商1。
<4> 第四步
余数左移之后减掉除数,而减法之后又得到了新的余数和除数是异号的。
异号的情况下,我们可以确定要商0。
余数左移,并且加上除数,这样我们又得到了一个余数。
最后这个余数和除数是异号的。所以理论上最后这一位的商我们应该商0。
但是在补码的加减交替法当中,最后这一位的商会进行一个比较特殊的处理,我们会把它恒置为1。
就是并不会管最后这一步得到的余数和除数到底是同号还是异号,我们都会把最后这一位商置为1,
这么做的好处是省事,并且我们末位恒置为1,这样的操作带来的误差也不会超过 2 的负 n 次方。
具体为什么,大家可以结合每一位的位权来进行一个思考。
总之补码的加减交替法,最后这一位的商,我们都会统一的恒置为1。
在原码的加减交替法当中,如果最后余数它的正负性有问题,我们可能还需要恢复余数。
但是在补码的除法当中,最后这一步我们恒置为1,除法运算就可以到此结束,我们也不需要再管最后的余数到底需不需要恢复这样的问题。
所以补码的除法末位恒置为 1 还是能省一些事的,可以让硬件电路设计起来更简单一些。
通过这一堆的操作,我们可以得到 x 除以 y 的商的补码表示,应该是 1. 0101,余数是 0. 0111 乘以 2 的-4,也就是 2 的负 n 次方。
我们最终得到的余数,它的符号位也会直接反映出这个余数我们应该取正还是取负。
四、总结回顾
大家可以在自己捋一捋原码的加减交替法和补码的加减交替法的一些类似的地方,还有不一样的地方。
最重要的区别就是上商还有确定下一步应该加还是应该减的一个原则。
对于原码的加减交替法来说,我们只会通过当前的余数的正负性来确定下一步应该商 0 还是商1。同样也是根据余数的正负性来确定下一步应该加还是应该减。
而对于补码加减交替法来说,我们会根据余数和除数它们是否同号,来确定应该商 0 还是商1,同时也确定下一步应该加还是应该减。
另外一点,原码的加减交替法当中,我们最后那一步如果发现余数为负,还需要加上除数的绝对值来恢复余数。
而补码加减交替法当中,商的末位恒置为1,也不存在恢复余数这样的步骤。
所以这就导致了原码加减交替法当中,我们加减的次数有可能是 n 加 1 次,也有可能是 n 加 2 次。
但是补码加减交替法当中,加减的次数一定只有 n 加 1 次。