一、计算机加法的实现:
(1).一位二进制加法
首先给出一位二进制加法的真值表,然后我们通过分析真值表来得出如果进行二进制加法的规则。
一位二进制加法真值表:(对应于硬件中的半加器)
x | y | sum | carry |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 |
分析上面一位二进制加法的真值表,可以看出和其实就是x XOR y的结果。而进位恰好是x AND y的结果。下面提供XOR和AND的真值表,进行验证。
x XOR y真值表:
x | y | output |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
x AND y真值表:
x | y | output |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
(2).多位二进制加法
因此进行一位二进制加法,只要对x和y进行XOR和AND运算就可以得出和以及进位。如果要求多位的二进制加法,则要把低位传上来的进位也要计算进去。修改一位二进制加法的真值表,在输入部分中加入低位传入的进位,然后对计算结果进行修正就可以得到多位二进制加法的真值表了。
多位二进制加法真值表:(对应于硬件中的全加器)
x | y | icarry | sum | ocarry |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 | 0 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 | 1 |
1 | 1 | 0 | 0 | 1 |
1 | 1 | 1 | 1 | 1 |
分析上面的真值表就可以总结出,多位二进制加法的规则了。如下:
sum = (x XOR y) XOR icarry
ocarry = (x AND y) OR (icarry AND (x XOR y)) = (x AND y) OR (y AND icarry) OR (icarry AND x)
利用以上的分析结果,可将x, y的每一位级联计算,先计算x和y的第零位,该位的输入进位(icarry=0)为零,将计算所得的进位传入到x和y的第一位的计算中,依次进行直到计算完最高位为止,此时将每一位计算所得的和连接起来就是最终的和,最高位计算所得的进位就是最终的进位。至此,二进制的加法应该没有什么问题了,很简单XOR为和,AND为进位。计算机中的加法也是使用这种原理来实现的,有兴趣的可以看看《编码的奥秘》这本书。
(3).在C++中实现加法
通过以上分析似乎用代码实现计算机加法的方法已经很明了了,将参加计算的x,y分别一位一位的进行XOR和AND,然后将结果打印出来,OK完事了,很简单不是吗?但问题是如果将x和y的每一位拆分,然后记录每一位计算后所得的进位,然后做为下一位的进位输入。仔细想想又似乎是问题多多啊,烦啊。其实也不然我们用代码实现的时候已经不需要再将每一位拆分计算了(如果你确实像模拟计算机硬件的执行过程也可以这么做,只是这样做很麻烦,而且计算的速度比较慢)。首先,我们通过对x和y进行&位运算,得出每一位上的进位。然后对x和y进行^位运算,得出没有加进位的和。最后将所得的和当做新的x,所得的进位往左移一位(第零位的进位输入为0)当做新的y,继续做上面的步骤,直到进位为0,此时x中保存的就是我们要求的x和y的和了。
具体的实现代码也很简单,如下:
int add(int a, int b)
{
int sum = a;
int carry = b;
while(carry)
{
int tmps = sum;
sum = tmps ^ carry;
carry = (tmps & carry) << 1;
}
return sum;
}
二、计算机减法的实现
(1).减法概述
减法是加法的逆运算,加法中有进位,相应的减法中需要借位。加法中的进位可以从低位开始依次往上传递,而且高位对低位的计算不产生影响。而减法的借位,则需要从高位获得,高位会对低位的计算产生影响。如果是小数字减大数字则计算过程更复杂,因此直接实现减法对计算机来说很复杂,而且效率很低。那计算机要如何实现减法呢?也许你们听说过2-补码,这就是计算机要施展的魔法,摇身一变,很难实现的减法运算将变成加法。然后直接利用上一节实现的加法过程完成计算。什么是补码?取反运算就是最简单的补码。2-补码比取反多了一个步骤,即取反后加1。还是用实例来说话吧。下面是8位(即一个字节)的2-补码编码以及所表示的数字,一个字节能表示从-128到127的数字。如果要更详细的了解2-补码那就看这里。
二进制十进制
10000000 -128
10000001 -127
10000010 -126
10000011 -125
11111101 -3
11111110 -2
11111111 -1
00000000 0
00000001 1
00000010 2
01111100 124
01111101 125
01111110 126
01111111 127
通过上面的表,不难看出只要想得到一个数的相反数,只要对这个数求2-补码就可以了,即取反加1操作。然后继续对得到的结果进行该操作就可以得到原来的数字。通过使用2-补码形式的编码,我们可以把减法运算顺利的转换成加法。只要对减数求2-补码,然后跟被减数相加即可得到差值。不信,那就验证一下 例如 124 - 127 = -3 。我们利用上面的规则来进行验证一下,先算出减数的2-补码,减数为127,对其求2-补码后为-127(10000001)。然后跟124(01111100)执行加法操作。即可获得我们需要的差值 01111100 + 10000001 = 11111101(-3)刚好获得的结果就是-3的编码。这里就不举更多的例子了,有兴趣的可以自己验证。
(2).减法在C++中的实现
通过上面的研究,要在代码中实现减法已经很明了,很简单了。第一步对减数取反然后加1,第二步将第一步所得值和被减数相加。而加法运算已经在上一节实现过,具体代码如下:
int subtract(int a, int b)
{
int subtrahend = add(~b, 1);
int sub = add(a, subtrahend);
return sub;
}
三、计算机乘法的实现
(1).原始的乘法实现
乘法最简单的理解就是,将被乘数加乘数次即可得到乘积。考虑到负整数的乘法,我们这里先对乘数和被乘数求绝对值,然后对绝对值进行上述的乘法操作。确定乘积符号的规则为同号为正,异号为负。这中实现方式比较简单,代码如下:
int multiply(int a, int b)
{
//将乘数和被乘数都取绝对值
int multiplier = a < 0 ? add(~a , 1) : a;
int multiplicand = b < 0 ? add(~b, 1) : b;
//计算绝对值的乘积
int product = 0;
int count = 0;
while(count < multiplier)
{
product = add(product, multiplicand);
count = add(count, 1);
}
//计算乘积的符号
if((a ^ b) < 0)
{
product = add(~product, 1);
}
return product;
}
(2).改进的乘法实现
上面的第一种实现方式虽然简单,但是效率太低。如果乘数和被乘数小一点还可以,如果大了那效率是不能忍受的。这里要实现的这种乘法,最多做log(n)次的加法操作,就可以求出乘积。这种方式和第一方式的相同点是,这里也是先对两数的绝对值就乘积,最后确定符号。这种实现方式就是对手动计算乘数的模拟。具体步骤如下:
1)根据乘数每一位为1还是为0,决定相加数取被乘数移位后的值还是取0;
2)各相加数从乘数的最低位开始求值,并逐次将相加数(被乘数)左移一位,最后一步求和
3)符号位根据同号为正异号为负的原则
如果对上面的步骤还很难明白的话,那我下面举个简单的例子。对照着例子然后在仔细分析下上面的计算步骤,将不难明白这种实现方式的原理。例如 11 * 13 = 143 ,转换成二进制 1011 * 1101 =10001111 ,首先判断乘数的第一位,这里第一位为1,因此第一个相加数为1101。然后判断乘数的第二位,这里第二位为1,所以第二个相加数为11010,即被乘数左移一位。继续看乘数的第三位,这里第三位为0,则第三个相加数为0。最后看乘数的第四位,这里为1,所以最后一个相加数为1101000即被乘数左移三位。最后将获得的所有相加数都加起来,而这个和就是所要求的乘积。我们验证一下是否正确,将我们算出的各个相加数依次加起来。(1101+11010+0+1101000=10001111)哦,果然和上面给出的一样,我们把二进制转成十进制即为143。不难看出来相加数就是根据乘数的第n位是否为1,如果为1,就将被乘数左移n位所得(这里n从0开始)。
计算过程演示:
1 0 1 1
* 1 1 0 1
------------------
1 1 0 1
1 1 0 1 0
0 0 0 0 0 0
1 1 0 1 0 0 0
------------------
1 0 0 0 1 1 1 1
C++中的实现代码如下:
int multiply(int a, int b)
{
//将乘数和被乘数都取绝对值
int multiplier = a < 0 ? add(~a , 1) : a;
int multiplicand = b < 0 ? add(~b, 1) : b;
//计算绝对值的乘积
int product = 0;
while(multiplier)
{
if(multiplier & 0x1)
{
product = add(product, multiplicand);
}
multiplicand = multiplicand << 1;
multiplier = multiplier >> 1;
}
//计算乘积的符号
if((a ^ b) < 0)
{
product = add(~product, 1);
}
return product;
}
四、计算机除法的实现
x/y其实就是,x不断减y的过程。小学时候学的长长除法就是这个原理。
用二进制的除法x/y,比十进制容易写,商不是0即是1,而且如果除数大于除数的1倍,商就是标记在另一个位上面了
二进制除法x/y=0.1001/0.1011手工计算如下
0.11
_
0.1001/0.1001
10010(后面补0)
-1011
——
111(余数)
1110(后面补0)
-1011
——–
1(余数)
设ri表示第i次运算后所得的余数,则:
若ri>0,则商1,余数和商左移1位,再减去除数,即ri+1=2ri-y
若ri<0,则商0,余数和商左移1位,再加上除数,即ri+1=2ri+y
用85/6来举例,85/6=1010101/110
a.101(0101)左移1位到第3位都小于110,因此商=000
b.1010(101)左移四位是1010,比110大,商=0001,余数=1010-110=100(101)
c.余数100(101)左移一位是1001,比110大,商=00011,余数=1001-110=11(01)
d.余数11(01)左移一位是110,等于110,商=000111,余数=0(1)
e.余数0(1)左移一位是01,小于110,商=0001110,余数=01
因此85/6=1010101/110=0001110,即14,余数为最后的余数1