您的位置:首页 > 其它

不用 +,-,*,/四则运算实现加法

2014-07-18 22:56 309 查看
问题:不用基本四则运算 + - * / 实现加法运算。

1.我的思路

我想到的一种办法是,使用二进制的位运算来避开使用基本四则运算。任何数字可以表示为二进制(废话,在内存里面,什么数不是二进制表示的?)。考虑 a + b,写成二制制形式:

011010010 + 101010110,两个数字的相同对应位分别进行计算,包括第 i 位的 ai,bi,和第 i - 1 位上的进位 ci-1,有以面两个东西需要求解:

1.当前位的数字即为:ai ^ bi ^ ci-1。

2.当前位的进位。为了求这个值,我们来看一下,只有 ai,bi,ci-1 这三个数字中,有二个或三个 1 时才会发生进位,即 ci 为1。为了得到 ci 的等式,我们列出值表如下:

ai bi
ci-1 异或 与
或 情况说明

0 1 1
0 0
1 两个1,进位为1

1
1 1 1
1 1
三个1,进位为1

0 0
0 0 0 0 零个1,进位为0

0 0
1 1 0 1 一个1,进位为0

...其它重复的情况就不写出,ai bi ci-1 可以认为只有四种取值:三者中有一个1,两个1,三个1,零个1

由上面的表,可以得出结果,能够进位的,只有:“异或值”等于“与值”,且“或值”为1。因此,可以写出下面的等式:

ci = ((ai ^ bi ^ c-1) == (ai | bi | ci-1) &&(1== ai | bi | ci-1))。

这种思路的源代码如下:

也许你想到了以下三点:

1.最高位(符号位)也使用上面的运算步骤,是否是正确的行为。

2.倒数第二位向最高位进位,并且最高位接受这个进位并参与计算,是否是正确的行为。

3.最高位计算时还有进位,也就是溢出了,而上面的运算步骤没有处理这个溢出,是否是正确的行为。

这些问题加起来其实是一个最本质的问题:为什么数字的补码形式直接运算是正确的运算?我们假定这是正确的,那么,上面的步骤必然也是正确的,因为我们完全是在模拟这个步骤。

如果你还是想知道为什么数字的补码运算是正确的运算,请参考我的另一篇文章:《补码的本质》。

2.拿来主义-何海涛的解法(在此

我认为何海涛的这个算法比我的算法的层次要高,要更抽象,更注重整体,因而是更好的解法。我的算法是完全模拟运算,而何的解法虽然也是完全模拟,但层次比我的高,中间有些跳跃。来看一下他的解法。

15 + 185 可以看成以下三个计算:

1.不考虑进位

1 5 1 5

+ 8 5 8 5

_______

9 0 9 0

2.考虑所有的进位,将第一步中的结果,加上所有的进位;上面的计算中,所有的(或者称为总体的)进位包括,个位和百位上的进位,分别等于10,1000,那么,这个总体的进位等于 10 + 1000

因此,最终的结果等于 9090 + (10 + 1000)

3.递归完成第二步中新产生的相加式

这三个步骤对于所有的 a + b 都是成立的。有了这个思路,程序就好写了。关键在于,如何求那个总体的进位。其实这个也好求,放到二进制中去看,只有加数的两个二进制位(ai , bi )都是 1 时,才会有进位,因此,直接使用 (ai & bi) << (i+ 1) 即得到第 i 位上的进位。

而总体的进位即是对 (ai & bi) << (i+ 1) 进行求和,即总体进位c = (a1 & b1) << 2 + (a2 & b2) << 3 + (a3 & b3) << 4 + ....= (a & b)
<< 1
,即直接使用 (a & b) << 1 即可得到总体进位。

上面的求解揭示了以下结论:

1.一位二进制 1 和 0 相加的第一步结果(不进位结果)是 1 ^ 0;多位二进制数 a 和 b 相加的第一步结果(不进位结果)是 a ^ b。

2.一位二进制 1 和 0 相加总体进位是 (1 & 0) << 1;多位二进制数 a 和 b 相加的第一步结果(不进位结果)是 (a & b)<<1。

据此,我们可以得到很大的一个启发:满足一位二进制的运算规则,都满足由二进制构成的数字的运算规则。

这里似乎是一个哲学的观点:(最小粒子)的规则 与(纯粹由这个最小粒子构成的系统)的规则是完全一致的。

给出这种实现的代码如下:

3.拿来主义-163博友(superddr)之法

这位网友的方法很精妙,说破了很简单,但能想到这个方法的人实在独具慧眼!

他利用了“数组下标”天生含有的加法功能。如 d[1] = *(d + 1)这里就有加法了,如果要实现 a + b 只需要将 a 和 b 纳入到数组下标中去计算即可。

先把握一点,数组下标中的加法是内存地址的加法,所以 a 和 b 必须都要与地址挂勾。我们先设 a 为(类型 A 的数组s的首地址)的值,如 0x000800110,也即 A *a = 0x000800110,那么 s[b]就表示取数组 s 的第 b 个元素,这个元素的地址是 &(s[b]),它是由首地址 &(s[0]) (这个值等于 a)向内存增长方向增加 b * sizeof(A) 字节的内存地址。 也即 a + b * sizeof(A) =
&(s[b]);如果我们令 sizeof(A ) 为 1,那么 &(s[b]) 就是 a + b 的结果,所以 A 类型可以选择为 char 类型。

下面是代码:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: