这篇的话主要算是上一篇浮点数的前传吧,补充一下补码的定点数运算规则(加减乘除),然后会在上一篇浮点数里加一些例子
文章目录
- 定点数表示
- 定点数加法
- 定点数减法
- 溢出判断
- 单符号位法
- 双符号位法
- 定点数乘法
- 定点数除法
- 恢复余数法
- 加减交替法
- 参考资料
定点数表示
定点数表示小数算是很直观地一种表示形式吧,类比于十进制表示的小数 d m d m − 1 . . . d 1 d 0 . d − 1 d − 2 . . . d − n d_md_{m-1}...d_1d_0.d_{-1}d_{-2}...d_{-n} dmdm−1...d1d0.d−1d−2...d−n,这里 d i d_i di 的取值范围是 0~9,那么二进制相似的表示 b m b m − 1 . . . b 1 b 0 . b − 1 b − 2 . . . b − n − 1 b − n b_mb_{m-1}...b_1b_0.b_{-1}b_{-2}...b_{-n-1}b_{-n} bmbm−1...b1b0.b−1b−2...b−n−1b−n 就是定点数表示法了,这里 b i b_i bi 的取值范围是 0 和 1。
那么小数点左边的位的权就是 2 的正幂,而右边的位的权是 2 的负幂。例如, 101.1 1 2 101.11_2 101.112表示数字 1 × 2 2 + 0 × 2 1 + 1 × 2 0 + 1 × 2 − 1 + 1 × 2 − 2 = 4 + 0 + 1 + 1 2 + 1 4 = 5 3 4 1 \times 2^2 +0 \times 2^1 + 1 \times 2^0 + 1 \times 2 ^ {-1} + 1 \times 2^{-2}=4+0+1+\frac{1}{2}+\frac{1}{4}=5\frac{3}{4} 1×22+0×21+1×20+1×2−1+1×2−2=4+0+1+21+41=543。
我们知道在有限长度的编码下,十进制表示法不能准确地表达像 1 3 \frac{1}{3} 31和 5 7 \frac{5}{7} 75这样的数。类似,小数的二进制表示法只能表示那些能够被写成 x × 2 y x \times 2^y x×2y。其他的值只能够被近似地表示,例如,数字 1 5 \frac{1}{5} 51可以用十进制小数 0.20 0.20 0.20 精确表示,但是,我们不能把它准确地用二进制小数来表示,只能近似地表示它。
当然,增加二进制表示的长度可以提高表示的精度:
上面讨论的情况因为是正数,所以其原码形式可以看作是和补码形式是一致的,那么对于负数,其实和非小数的补码表示也是一致的,我们在前面加上符号位(0 表示正数,1 表示负数),后面的位就是原码取反+1。
定点数加法
首先给定两个规则:
- 规则 1: [ 和 ] 补 = [ 加数 ] 补 + [ 加数 ] 补 [和]_{补}=[加数]_{补}+[加数]_{补} [和]补=[加数]补+[加数]补
- 规则 2: [ − y ] 补 = [ y ] 补 整体取反再 + 1 [-y]_{补}=[y]_{补}整体取反再+1 [−y]补=[y]补整体取反再+1
那么对于规则 1,我们可以引申出如下的扩展规则:
- [ x + y ] 补 = [ x ] 补 + [ y ] 补 [x+y]_{补} = [x]_{补} + [y]_{补} [x+y]补=[x]补+[y]补
- [ x − y ] 补 = [ x ] 补 + [ − y ] 补 [x-y]_{补} = [x]_{补} + [-y]_{补} [x−y]补=[x]补+[−y]补
- [ − x + y ] 补 = [ − x ] 补 + [ y ] 补 [-x+y]_{补} = [-x]_{补} + [y]_{补} [−x+y]补=[−x]补+[y]补
- [ − x − y ] 补 = [ − x ] 补 + [ − y ] 补 [-x-y]_{补} = [-x]_{补}+[-y]_{补} [−x−y]补=[−x]补+[−y]补
对于规则 2,这里整体取反是包括符号位(毕竟加了个负号,符号位肯定要改变呀),举一个例子: [ Y ] 补 = 0 , 1100010 B [Y]_{补} = 0, 1 1 0 0 0 1 0 B [Y]补=0,1100010B [ − Y ] 补 = 1 , 0011110 B [-Y]_{补}=1, 0 0 1 1 1 1 0 B [−Y]补=1,0011110B
从一个例子入手,已知 X、Y 为两个带符号定点整数,它们的原码为 [ X ] 原 = 0 , 0011001 B [X]_{原}=0, 0011001B [X]原=0,0011001B, [ Y ] 原 = 1 , 0000100 B [Y]_{原}=1, 0000100B [Y]原=1,0000100B,求 [ X + Y ] 原 [X+Y]_原 [X+Y]原
-
首先从原码得到X、Y各自的补码 [ X ] 补 = 0 , 0011001 B [X]_{补}=0, 0011001B [X]补=0,0011001B, [ Y ] 补 = 1 , 1111100 B [Y]_{补}=1, 1111100B [Y]补=1,1111100B
-
那么得到补码之后就可以直接相加, [ X + Y ] 补 = ( 1 ) 0 , 0010101 B [X+Y]_{补}=(1)0, 0010101B [X+Y]补=(1)0,0010101B,括号里的 1 是符号位的进位,舍去即可
-
符号位是 0,说明是正数,那么其原码就和补码是一样的, [ X + Y ] 原 = 0 , 0010101 B [X+Y]_{原}=0, 0010101B [X+Y]原=0,0010101B
定点数减法
从上面定点数加法的规则 1 的扩展规则中,我们知道 [ x − y ] 补 = [ x ] 补 + [ − y ] 补 [x-y]_补=[x]_补+[-y]_补 [x−y]补=[x]补+[−y]补,那么实际就是把减法转化为加法去做。
还是举一个例子:
已知 [ X ] 补 = 0 , 1110111 B [X]_补=0, 1110111B [X]补=0,1110111B, [ Y ] 补 = 0 , 1100100 B [Y]_补=0, 1100100B [Y]补=0,1100100B,求 [ X − Y ] 补 [X-Y]_补 [X−Y]补
- 首先求出 [ − Y ] 补 = 1 , 0011100 B [-Y]_补 = 1,0011100B [−Y]补=1,0011100B
- 将 [ X ] 补 [X]_补 [X]补和 [ − Y ] 补 [-Y]_补 [−Y]补相加,得到 [ X − Y ] 补 = 0 , 0010011 [X-Y]_补 = 0, 0010011 [X−Y]补=0,0010011
溢出判断
单符号位法
看完简单的定点整数补码加法和减法,我们看下面这个例子:
[ X ] 补 = 1 , 0011100 [X]_补 = 1, 0011100 [X]补=1,0011100, [ Y ] 补 = 1 , 0001100 [Y]_补=1, 0001100 [Y]补=1,0001100,求 [ X + Y ] 补 [X+Y]_补 [X+Y]补
按照之前的计算规则,这里我们得到 [ X + Y ] 补 = 0 , 010100 [X+Y]_补=0, 010100 [X+Y]补=0,010100,问题很明显,两个负数相加结果得到一个正数,这里就是发生了溢出,如果是两个负数相加最终得到个正数,就是负溢出,如果是两个正数相加最终得到个负数,就是正溢出。
那么这里其实就是单符号位的溢出判断,根据计算前的正负性和计算后的正负性得到溢出结果。
双符号位法
这里引入双符号位:
- c s + 1 c_{s+1} cs+1:符号位运算后是否有进位,有进位时 c s + 1 = 1 c_{s+1}=1 cs+1=1,无进位时 c s + 1 = 0 c_{s+1}=0 cs+1=0
- c s c_s cs:数值位的最高位运算后是否进位,有进位时 c s = 1 c_s=1 cs=1,无进位时 c s = 0 c_s=0 cs=0
那么我们可以根据加数的正负分为三种情况讨论:
- 正数 + 正数:首先符号位都是 0,那么不可能有进位,即 c s + 1 = 0 c_{s+1}=0 cs+1=0,而 c s c_s cs 是根据数值位的最高位运算后是否有进位得到,那么如果有进位,说明结果的符号位是 1,即两个正数相加得到了负数,说明正溢出了,如果没进位,则未发生溢出。
- 正数 + 负数:首先我们知道 正数+负数 一定不会产生溢出,如果 c s + 1 = 1 c_{s+1}=1 cs+1=1,则 c s = 1 c_{s}=1 cs=1;如果 c s + 1 = 0 c_{s+1}=0 cs+1=0,则 c s = 0 c_{s}=0 cs=0,即 c s c_s cs 和 c s + 1 c_{s+1} cs+1 一定相同。
- 负数 + 负数:符号位都是 1,那么符号位相加一定会产生进位,即 c s + 1 = 1 c_{s+1}=1 cs+1=1,而 c s c_s cs 也是要根据数值位的最高位运算后是否有进位得到,如果无进位,说明结果的符号位是 0,即两个负数相加得到了正数,说明负溢出了, 如果有进位,则未发生溢出。
这样我们就可以总结出规律来了,如果 c s = c s + 1 c_s = c_{s+1} cs=cs+1 则未发生溢出,反之则发生溢出。
还是通过一个例子:已知 [ X ] 补 = 1 , 0011100 [X]_补=1, 0011100 [X]补=1,0011100, [ Y ] 补 = 0 , 1110001 [Y]_补=0, 1110001 [Y]补=0,1110001,求 [ X − Y ] 补 [X-Y]_补 [X−Y]补
- 求 [ − Y ] 补 = 1 , 0001111 [-Y]_补 = 1, 0001111 [−Y]补=1,0001111
- 求 [ X ] 补 + [ − Y ] 补 = 0 , 0101011 [X]_补+[-Y]_补 = 0, 0101011 [X]补+[−Y]补=0,0101011,双符号位分别是 c s = 0 c_s=0 cs=0, c s + 1 = 1 c_{s+1}=1 cs+1=1,可以看到这两个符号不一致,说明产生了溢出,从单符号位法出发,两个负数相加得到了正数,说明发生了负溢出。
定点数乘法
依旧是从例子入手,这里先不把符号转化为符号位, x = − 0.1101 x=-0.1101 x=−0.1101, y = 0.1011 y=0.1011 y=0.1011,求 x ⋅ y x \cdot y x⋅y:
那么看出规律,从 y y y 的低位开始,与 x x x 相乘,再向右移位,一直到 y y y 的最高位计算完,移完位结束。
这里引入两个寄存器,ACC —— 累加器,MQ —— 乘商寄存器,则上面例子的流程如下:
- 初始 ACC 是全 0,MQ 里保存乘数 y y y
- 取出 MQ 的末位,是 1,那么 ACC 里要加上 x x x (计算过程中不考虑符号)
- 右移 1 位,移到 MQ 里了一个 1(这是结果的末位)
- 取出 MQ 的末位,还是 1,ACC 加上 x x x
- 右移 1 位,移到 MQ 里一个 1
- 取出 MQ 的末位,是 0,那么就是加上 0 了,然后右移 1 位,移到 MQ 里一个 1
- 取出 MQ 的末位,是 1,那么再加上 x x x
- 右移 1 位,移到 MQ 里一个 1
这样我们得到最终的结果 − 0.10001111 -0.1000 1111 −0.10001111。
在此基础上引入补码的定点数乘法运算规则:
1) 符号位参与运算
2)被乘数与部分积(也就是ACC里面的数)使用双符号位,乘数(MQ里的数)使用单符号位
3)乘位末位增设附加位,初始为 0
4)算术移位
5)最后一步不移位
6)乘数次低位 - 最低位,如果 =1 则 + [ − x ] 补 +[-x]_补 +[−x]补,如果 =0 则 + 0 +0 +0,如果 =-1 则 + [ x ] 补 +[x]_补 +[x]补
那么对于上面的例子,我们有 [ x ] 补 = 11.0011 [x]_补=11.0011 [x]补=11.0011, [ − x ] 补 = 00.1011 [-x]_补=00.1011 [−x]补=00.1011,对应相乘过程如下:
- 初始 ACC 和 MQ 里的数如下
- 次低位 - 最低位为 1,那么 + [ − x ] 补 +[-x]_补 +[−x]补
- 算术右移,移到 MQ 一个 1
- 次低位 - 最低位为 0,那么 + 0 +0 +0,那么直接右移,移到 MQ 一个 0
- 次低位 - 最低位为 -1,那么 + [ x ] 补 +[x]_补 +[x]补
- 算术右移,移到MQ一个0
- 次低位 - 最低位 1,那么 + [ − x ] 补 +[-x]_补 +[−x]补
- 算术右移,移到MQ一个0
- 次低位 - 最低位 -1,那么 + [ x ] 补 +[x]_补 +[x]补,最后一步不需要再右移了
最终得到双符号位补码 11 , 01110001 11, 0111 0001 11,01110001
定点数除法
恢复余数法
首先看下原码形式下的定点数除法,假设被除数 [ x ] 原 = 0.1011 [x]_原 = 0.1011 [x]原=0.1011,除数 [ y ] 原 = 0.1101 [y]_原 = 0.1101 [y]原=0.1101:
- (按照十进制除法的方式) 首先尝试取 1,发现不行
- 那么只能取 0,再尝试取 1,除数相当于右移了一位,可行
- 再尝试取 1,除数相当于右移一位,可行
- 尝试取 1,除数相当于右移一位,不可行,取 0
- 尝试取 1,除数相当于右移一位,可行,此时得到商 0.1101 0.1101 0.1101,余数 0.00000111 0.00000111 0.00000111
上面方法很直观,但实际应用中,我们不希望占用寄存器的位数是逐渐增加的,这里引入恢复余数法。
看这个例子: x = 0.1011 x=0.1011 x=0.1011, y = 0.1101 y=0.1101 y=0.1101,求 x / y x / y x/y:
1) ∣ x ∣ − ∣ y ∣ = ∣ x ∣ + ( − ∣ y ∣ ) = [ x ] + [ − ∣ y ∣ ] 补 |x| - |y| = |x| + (-|y|)=[x]+[-|y|]_补 ∣x∣−∣y∣=∣x∣+(−∣y∣)=[x]+[−∣y∣]补, [ x ] 原 = 0.1011 [x]_原=0.1011 [x]原=0.1011, [ y ] 原 = 0.1101 [y]_原=0.1101 [y]原=0.1101,, ∣ x ∣ = 0.1011 |x|=0.1011 ∣x∣=0.1011, ∣ y ∣ = 0.1101 |y|=0.1101 ∣y∣=0.1101, [ − ∣ y ∣ ] 补 = 1.0011 [-|y|]_补=1.0011 [−∣y∣]补=1.0011
2)和上面的方法一样,先尝试取 1,减去 ∣ y ∣ |y| ∣y∣,相当于加上 [ − ∣ y ∣ ] 补 [-|y|]_补 [−∣y∣]补,得到一个负数,说明取 1 是不可行的,那么这里需要把减掉的 ∣ y ∣ |y| ∣y∣ 加回来。
3)下面我们左移余数,那么就相当于我们之前右移了除数,然后再尝试取 1,即减去 y y y,是正数,说明可行
4)左移余数,再尝试取 1,是正数,说明可行
5)左移余数,再尝试取 1,是负数,不可行,把 ∣ y ∣ |y| ∣y∣ 加回来
6)左移余数,再尝试取 1,是正数,说明可行,至此,得到商 0.1101 0.1101 0.1101,余数 0.00000111 0.00000111 0.00000111,这里因为左移了四次余数,所以最后余数要右移回去。
有恢复余数法,也相应地有不恢复余数法,规则如下:
- 余数 < 0,余数左移再 + ∣ y ∣ +|y| +∣y∣
- 余数 > 0,余数左移再 − ∣ y ∣ -|y| −∣y∣
加减交替法
上面的恢复余数法中符号位是单独计算的,而在加减交替法中,符号位是参与到计算中的,其规则如下:
- 数据都用补码表示,符号位参与运算。
- 初始时观察被除数与除数的符号,同号做减法,异号做加法。
- 若余数与除数同号,则商 1,余数左移一位减去除数;若异号,则商 0,余数左移一位加上除数。
- 重复第三步操作直到得到 n 位商 (不包含符号位)。
- 商的末尾恒置 1。
看一个例子: x = − 0.1001 x = -0.1001 x=−0.1001, y = 0.1101 y=0.1101 y=0.1101,求 [ x / y ] 补 [x/y]_补 [x/y]补:
- [ x ] 补 = 1.0111 [x]_补=1.0111 [x]补=1.0111, [ y ] 补 = 0.1101 [y]_补=0.1101 [y]补=0.1101, [ − y ] 补 = 1.0011 [-y]_补=1.0011 [−y]补=1.0011
- 初始被除数与除数的符号是异号,做加法
- 可以看到余数与除数同号,商 1,余数左移一位减去除数
- 余数与除数异号,商 0,余数左移一位加上除数
- 余数与除数同号,商 1,余数左移一位减去除数
- 余数与除数异号,商 0,这样我们就得到了商 1.0101 1.0101 1.0101,因为商的末位要恒置 1,那么余数左移一位加上除数再算术右移4位就是最终的余数 1.11111111 1.1111 1111 1.11111111
参考资料
《深入理解计算机系统》
【原理6.7】补码的运算规则;补码加法;补码减法;补码运算
【原理6.8】溢出的判断;数据溢出;什么是溢出;溢出的处理;双符号位法;
定点数的乘法运算
定点数的除法运算(恢复余数法,加减交替法)