Java只使用位运算实现加减乘除

前言

接前面一篇博客,这又是某个公司的奇葩面试题(都说了到底是哪家公司才会出这种没营养的面试题)。不过吐槽归吐槽,这个题目还是有点学问的,比前面那个 不使用比较运算符如何比较两个数的大小 强多了(但是还是能看出面试官是在存心刁难人)。

原题是“只给加法,如何实现减乘除”,我寻思着,既然减乘除都不给了,那就加大点难度,加法也别给了吧,今天就手动去实现加减乘除。这里只实现int类型的加减乘除。

一说到这种“不给xxx如何实现xxx的问题”,那第一个想到的就是位运算,因此本篇博客的各种运算也是基于位运算的。关于位运算的知识点,请阅读本博客的看官们自行百度学习。。。。

加法

我们先举个加法的例子:8+9=17,可以转换成10+7,即:二者相加不考虑进位的值,和二者相加只考虑进位的值相加。我们再通过二进制来直观的看一下。

0000 1000 (8)
0000 1001 (9)
0001 0001 (17)

下面描述一下思路(可能有点绕)。

首先我们看,直接二进制相加的结果,0+0=0,1+0=1,1+1=10。好像能看出点什么。前两个的运算规则复合“异或运算”,而后者则复合与运算并左移1位。

到现在思路就清楚了:a^b的结果是不考虑进位的结果,而a&b<<1是只考虑进位的结果。把二者相加即可。如果相加后,可能还存在进位,那就让这两个数字继续相加,一直到进位为0为止。这里使用递归去实现,感兴趣的可以用循环实现,性能比递归要高。

    public int add(int a, int b) {
        // 得到原位和
        int xor = a ^ b;
        // 得到进位和
        int forWoad = (a & b) << 1;
        return forWoad == 0 ? xor : add(xor, forWoad);
    }

到这里,加法就实现完毕了

负数

计算机中的负数实现,是将正数按位取反获取反码,之后+1获得补码,这个结果就是某个正数所对应的负数。(这个在计算机组成原理、操作系统、计算机导论、离散数学等课本中都有,不记得的请翻一下大学课本。)。

负数的实现其实还是比较简单的,按位取反之后+1即可。

    public int negative(int num) {
        return add(~num, 1);
    }

减法

实现了负数之后,我们第一步实现的加法就可以和负数进行运算了,而减法也就变得简单起来。

减法的实现如4-2等价于4+(-2),我们直接使用加法和负数就可以实现。

    public int minus(int a, int b) {
        return add(a, negative(b));
    }

绝对值

接下来要实现乘法和除法。乘法和除法可能会有正数和负数相互计算的情况,因此我们实现乘除之前,需要先实现绝对值计算的功能,将运算数字转换成绝对值进行乘除,之后判断是否需要加上负号即可。

    public int abs(int num) {
        if (num < 0) {
            num = minus(0, num);
        }
        return num;
    }

乘法

乘法的实现,如11*10,乘法的流程如下面所示。

0000 1011 (11)
0000 1010 (10)
================
0001 0110 (1011<<1,相当于乘以0010)
0101 1000 (1011<<3,相当于乘以1000)

可以看到,二进制乘法的原理是:从乘数的低位到高位,遇到1并且这个1在乘数的右边起第i(i从0开始)位,那么就把被乘数左移i位得到temp_i,直到乘数中的1遍历完毕后,把根据各位1而得到的被乘数的左移值全部相加即得到乘法结果。

而至于存在负数的运算,可以先获取负数的个数,再将两个数字转换成绝对值计算,最后判断当负数是1个时,计算结果就是负数,其他情况则是正数。

    public int multi(int a, int b) {
        // 先获取负数的个数
        int negativeCount = negativeCount(a, b);
        // 负数转正数进行计算
        a = abs(a);
        b = abs(b);
        int i = 0;
        int res = 0;
        // 乘数为0则结束
        while (b != 0) {
            // 处理乘数当前位
            if ((b & 1) == 1) {
                res = add(res, a << i);
            }
            b = b >> 1;
            i = add(i, 1);
        }
        if (negativeCount == 1) {
            // 转为负数
            res = negative(res);
        }
        return res;
    }

negativeCount方法

    public int negativeCount(int a, int b) {
        int count = 0;
        if (a < 0) {
            count = add(count, 1);
        }
        if (b < 0) {
            count = add(count, 1);
        }
        return count;
    }

乘法事实上有个简单的实现。乘法就是个连加的过程,如5*4就是4个5相加,这个虽然性能比较低,但是操作起来简单,感兴趣的朋友可以自己去实现。

除法

除法没有什么简单的二进制实现方案,实际计算机中的除法也是通过连减去计算的。a/b的意义就是求a可以由多少个b组成,因此除法可以求a能减去多少个b。至于负数的情况,和乘法相同,不再介绍。

    public int sub(int a, int b) {
        // 先获取负数的个数
        int negativeCount = negativeCount(a, b);
        // 负数转正数进行计算
        a = abs(a);
        b = abs(b);
        int res;
        if (a < b) {
            return 0;
        } else {
            res = add(sub(minus(a, b), b), 1);
        }
        if (negativeCount == 1) {
            // 转为负数
            res = negative(res);
        }
        return res;
    }

取模

取模运算的思路和除法一样,也是个连减的过程,一直减到我们减不了为止,剩下的值就是我们要的结果。

    public int mode(int a, int b) {
        // 先获取负数的个数
        int negativeCount = negativeCount(a, b);
        // 负数转正数进行计算
        a = abs(a);
        b = abs(b);
        int res;
        if (a < b) {
            res = a;
        } else {
            res = sub(minus(a, b), b);
        }
        if (negativeCount == 1) {
            // 转为负数
            res = negative(res);
        }
        return res;
    }

结语

总的来说,加减乘除的实现还是比较简单的,只是对于初学者来说比较难想。熟悉了这类“不给xxx如何实现xxx”的题目之后,就能第一时间想到位运算了,通过位运算去实现运算符规则,实现起来就没有什么难度了。

最后还是需要点评一下这道题。这个问题相比上次的问题,稍微的有那么点水平,但是还是不难看出面试官在刁难人。程序员需要懂的原理,应该是开发中的各种框架原理,如HashMap、Spring、Mybatis等,理解了原理才能更好的优化、扩展,以便于提高性能。而所谓的加减乘除原理并没有这些重要,往往在上大学的时候也就了解过了。加减乘除原理的理解对性能优化帮助并不大,即使位运算性能比减法和除法高,但是这点性能损耗,在我们服务器动辄4g8g的情况下是没有任何区别的。所以说面试的时候别问这种刁难人的问题啊,你就是造造火箭问问Spring也好!

 

你可能感兴趣的:(Java,#,技术分享,java,位运算)