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

问题:不用基本四则运算 + - * / 实现加法运算。

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))。

这种思路的源代码如下:

#define MAX_BITS 32
typedef unsigned char bitInt;

/************************************************************************/
/* 
	根据当前位 a , b 以及上次的进位得到当前进位。
	根据实际情况,列出下面的表格:

	a	b	carry	  异或	   与		或		情况说明
	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
	
	...其它重复的情况就不写出,a b carry 可以认为只有四种取值:三者中有一个1,两个1,三个1,零个1

	由上面的表,可以得出结果,能够进位的,只有:“异或值”等于“与值”,且“或值”为1。
	这便是函数的实现。
*/
/************************************************************************/

int carry(bitInt a,bitInt b,unsigned int lastCurCarry)
{
	return (((a ^ b ^ lastCurCarry) == (a & b & lastCurCarry)) && (1 == (a | b | lastCurCarry)));
}

int specialAdd(int a ,int b)
{
	int result= 0;
	unsigned int curCarry = 0;
	bitInt curBitA,curBitB;
	for (int i =0;i < MAX_BITS;++ i,a >>= 1,b >>= 1)
	{
		curBitA = a & 1;
		curBitB = b & 1;
		result |= ((curBitA ^ curBitB ^ curCarry) << i);
		curCarry = carry(curBitA,curBitB,curCarry);
	}
	return result;
}

也许你想到了以下三点:

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。

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

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

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

//以下代码片段引自何海涛博客:http://zhedahht.blog.163.com/blog/static/254111742011125100605/
int AddWithoutArithmetic(int num1, int num2)
{
        if(num2 == 0)   //李志浩注:由于引起递归的因素是进位(不停地由加数和进位(非0)引发求新进位的问题),因此,
                        //此处使用进位等于 0 作为递推出口。
        {
            return num1;
        }
 
        int sum = num1 ^ num2;
        int carry = (num1 & num2) << 1;
 
        return AddWithoutArithmetic(sum, carry);
}

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 类型。

下面是代码:

int add1(int a,int b)
{
	char *c = (char *) a;
	return int(&(c[b]));
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值