本文前半部分转自http://zhedahht.blog.163.com/,版权归原作者所有。
题目:写一个函数,求两个整数的之和,要求在函数体内不得使用+、-、×、÷。
分析:这又是一道考察发散思维的很有意思的题目。当我们习以为常的东西被限制使用的时候,如何突破常规去思考,就是解决这个问题的关键所在。
看到的这个题目,我的第一反应是傻眼了,四则运算都不能用,那还能用什么啊?可是问题总是要解决的,只能打开思路去思考各种可能性。首先我们可以分析人们是如何做十进制的加法的,比如是如何得出5+17=22这个结果的。实际上,我们可以分成三步的:第一步只做各位相加不进位,此时相加的结果是12(个位数5和7相加不要进位是2,十位数0和1相加结果是1);第二步做进位,5+7中有进位,进位的值是10;第三步把前面两个结果加起来,12+10的结果是22,刚好5+17=22。
前面我们就在想,求两数之和四则运算都不能用,那还能用什么啊?对呀,还能用什么呢?对数字做运算,除了四则运算之外,也就只剩下位运算了。位运算是针对二进制的,我们也就以二进制再来分析一下前面的三步走策略对二进制是不是也管用。
5的二进制是101,17的二进制10001。还是试着把计算分成三步:第一步各位相加但不计进位,得到的结果是10100(最后一位两个数都是1,相加的结果是二进制的10。这一步不计进位,因此结果仍然是0);第二步记下进位。在这个例子中只在最后一位相加时产生一个进位,结果是二进制的10;第三步把前两步的结果相加,得到的结果是10110,正好是22。由此可见三步走的策略对二进制也是管用的。
接下来我们试着把二进制上的加法用位运算来替代。第一步不考虑进位,对每一位相加。0加0与1加1的结果都0,0加1与1加0的结果都是1。我们可以注意到,这和异或的结果是一样的。对异或而言,0和0、1和1异或的结果是0,而0和1、1和0的异或结果是1。接着考虑第二步进位,对0加0、0加1、1加0而言,都不会产生进位,只有1加1时,会向前产生一个进位。此时我们可以想象成是两个数先做位与运算,然后再向左移动一位。只有两个数都是1的时候,位与得到的结果是1,其余都是0。第三步把前两个步骤的结果相加。如果我们定义一个函数AddWithoutArithmetic,第三步就相当于输入前两步骤的结果来递归调用自己。
有了这些分析之后,就不难写出如下的代码了:
int AddWithoutArithmetic(int num1, int num2)
{
if(num2 == 0)
return num1;
int sum = num1 ^ num2;
int carry = (num1 & num2) << 1;
return AddWithoutArithmetic(sum, carry);
}
之前我的系列博客中有这么一道题,求1+2+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字以及条件判断语句(A?B:C)。刚兴趣的读者,可以到http://zhedahht.blog.163.com/blog/static/2541117420072915131422/看看。
本文后半部分转自http://www.cppblog.com/qingbizhu/archive/2012/03/30/168148.html ,版权归原作者所有。
最近,在看《剑指Offer——名企面试官精讲典型编程题》一书,当看到“面试题47:不用加减乘除做加法”时,经过一分钟左右思考后,得出了思路,跟书上一对照,基本一致,呵呵O(∩_∩)O~。于是,随即又开始思考:加法是实现了,那么减法、乘法还有除法又该怎么实现呢?一番思考与分析后,得出算法,写出代码,测试通过,Happy!!\(^o^)/~
【1. 加法】
代码1.1 加法:计算a+b
{
}
【2. 减法】
方案一:通过Add函数的实现
按常规思路,根据加减运算的互反性(即,减去一个数等于加上这个数的相反数),然后利用前面已实现的Add函数来进行实现。
按这个思路,我们首先要做到对一个整数取相反数,不能使用运算符“-”,那么就只能根据C++上两个互为相反数的int型数据的二进制结构关系——整数的相反数等于该数按位取反再加1,来设计如下的函数了。
代码2.1
{
}
然后就可以通过Add函数来实现减法了。
代码2.2 减法一:计算a-b
{
}
毕竟是作为对思维开放的一个锻炼,所以对于直接的减法算法的思考还是值得的。于是就有了下面的方案二。
方案二:直接通过位操作实现减法
算法是得通过二进制位计算来实现的,所以在分析减法时从二进制减法计算的角度去考虑将更合适。那么,两个二进制形式整数的减法操作又是怎样进行的呢?
- 首先,如果减数为0,则被减数即为减法的结果,运算结束。
但如果减数不为0,我们可以先把被减数和减数上同为1的位从两个数上去除。至于如何分离出值同为1的位,则可以通过求位与操作来做到;而把这些1分别中被减数和减数中去除,则可以通过按位异或来的操作来实现。 - 经步骤1处理后,被减数和减数在对应的位上,将或者通为0,或者分别为0和1,却不会同为1。此时:
如果对应位被减数=1,而减数=0,则所得结果对应位也为1;
如果对应位被减数=0,而减数=1,则所得结果对应位还是1,但此时须向前一位借1;
即,通过被减数与减数作位异或的操作得到临时结果,和通过对减数左移一位得到需从临时结果中减去的借数。 - 于是,经过步骤2后,原来的减法变成了要求:临时结果 - 借数
很明显,只要以临时结果为被减数,借数为减数,重复步骤1~3即可。
上述步骤中,如果被减数或减数为负数,由负数的二进制位结构,可以保证上述步骤的处理仍适用,证明过程就请恕我在这里略去了。具体的实现代码如下。
代码2.3 减法二:计算a-b
{
}
※注:上述加法和减法中,按代码安全性,其实还应考虑计算后数据溢出的情况,这里我偷了下懒,省去了。不过下面的乘除法,我会提供包含了异常处理的代码。异常处理的方式,我采用了throw抛出的方式。
为了方便对数据溢出的统一处理,在进行计算前,我先保存了被乘数与乘数的符号信息,并当被乘数或乘数为负时,利用上面的OppositeNumber函数,统一的转换为正整数(或0),然后再来进行乘法的运算。为了能同时适应32位和64位的整形数,在取符号信息与设置溢出判断位时,使用了以下的辅助宏和函数。
代码3.1 辅助宏与辅助函数
#define
int
{
}
乘法的算法。考虑到:
- 整数n乘以2k
== n << k - C++中的任何一个非负int型数据都可以表示为如下的形式:
k0×20+k1×21+...+km×2m
的形式。(其中:ki∈{0, 1}, i∈{0, 1, ... , m}, 32位int型m = 30, 64位int型m = 62)
于是,就可以利用乘法分配率,通过循环累加,进行乘法的运算了。参考代码3.2。
代码3.2 乘法:计算a×b
{
}
【4. 除法】
整数的除法,不同于乘法,除法所得的商的绝对值必然不大于被除数的绝对值,而所得余数的绝对值则必然小于除数的绝对值。所以,在设计除法函数的时候,无需考虑数据溢出的问题。但对于除法,却也有它自己的禁忌——除数不能为“0”。
为了处理的方便,准备工作同乘法一样,记录下被除数与除数的符号状态(比便在计算出结果后进行符号的调整),并当被除数或除数为负时,通过函数OppositeNumber将其转换为相反数。于是,接下来,我就只需考虑“非负整数(>=0)÷正整数(>0)”的情况了。对这种情况,计算过程如下:
- 预备工作:置商为0;
- 判断“被除数>=除数 ”是否成立:
成立,继续步骤3;
不成立,被除数的值赋给余数,计算结束。 - 备份除数,并设置商分子(一个临时变量,最终需加到商上面,故暂且如此命名)为1;
对商分子和除数同步向左移位,直到继续移位将大于被除数时为止; - 从被除数上减去除数,并将商加上商分子。
- 通过备份的除数值还原除数,跳转到步骤2继续执行。
对应的代码参加代码4.1。
代码4.1 除法:计算a÷b
{
}
※注:函数的返回值即为所求的商;