本文是继《一文了解有趣的位运算》的第二篇文章.html
咱们知道,计算机最基本的操做单元是字节(byte),一个字节由8个位(bit)组成,一个位只能存储一个0或1,其实也就是高低电平。不管多么复杂的逻辑、庞大的数据、酷炫的界面,最终体如今计算机最底层都只是对0101的存储和运算。所以,了解位运算有助于提高咱们对计算机底层操做原理的理解。java
两个二进制数异或运算的结果是不考虑进位时的结果,算法
两个二进制数与运算的结果中含有1的位是有进位的位。ide
以0101 + 0001 = 0110
为例分析以下:优化
//计算 0101 + 0001 0101 ^ 0001 = 0100 //异或结果代表,若是不考虑进位,那么结果为0100 0101 & 0001 = 0001 //与运算结果代表,最低位须要向次低位进1 0001 << 1 = 0010 //与运算结果左移一位,将进位加到高位上 //递归计算 0100 + 0010,直到+号右侧数字为0
java代码:code
递归htm
public static int add(int a, int b) { if (b == 0) { return a; } else { return add(a ^ b, (a & b) << 1); } }
循环blog
public static int add2(int a, int b) { int sum = a; while (b != 0) { sum = a ^ b; b = (a & b) << 1; a = sum; } return sum; }
与加法的思路一致,只不过减去一个数等于加一个数的相反数。递归
例如:5-1 = 5+(-1)。因此,咱们只须要求出被减数的相反数便可。ip
如何求出一个数的相反数?
计算机中存储的是二进制的补码形式,正数的补码与原码相同,负数的补码为对该数的原码除符号位外各位取反,而后在最后一位加1。
例如:
1在计算机中的二进制表示为:0000 0001
-1在计算机中的二进制表示为:1111 1111
计算过程为:
-1的原码:1000 0001
-1的反码:1111 1110
-1的补码:1111 1111
其中,由1的原码(0000 0001)取反可得-1的反码(1111 1110)
总结,一个数的相反数的求法就是该数每一位取反,末位加一。
java代码:
public static int minus(int a, int b) { return add(a, add(~b, 1)); }
若是没有思路,能够先在纸上笔算二进制乘法的过程:
0101 a × 0110 b ---------------- 0000 0101 0101 + 0000 ---------------- 00011110
梳理下笔算二进制乘法的过程:
初始化乘积结果为0,依次遍历数字b的末位 0→1→1→0,当末位为0时,乘积结果加上0,也就是乘积不变,A左移一位;当末位为1时,乘积结果加上A,A再左移一位。
如何遍历数字b的末位呢?
根据前面所学,咱们可使用与运算取一个数的末位,并不断右移数字b,直到数字b==0,便可结束位移。
须要注意的是正负数的符号问题,此处是先对a、b两数的绝对值计算其乘积,最后再肯定其符号。
java代码为:
public static int multiply(int a, int b) { //将乘数和被乘数都取绝对值 int A = a < 0 ? add(~a, 1) : a; int B = b < 0 ? add(~b, 1) : b; //计算绝对值的乘积 int P = 0; while (B != 0) { if ((B & 1) != 0) { //取乘数的二进制的最后一位,0 or 1 P = add(P, A); } A = A << 1; B = B >> 1; } //计算乘积的符号 if ((a ^ b) < 0) { P = add(~P, 1); } return P; }
最简单的除法实现就是不停的用除数去减被除数,直到被除数小于除数时,此时所减的次数就是咱们须要的商,而此时的被除数就是余数。
惟一须要注意的就是商的符号和余数的符号。商的符号肯定方式也乘法是同样,即同号为正,异号为负。而余数的符号和被除数的符号是同样的。
和简单的乘法实现同样,这里咱们要先对两数的绝对值求商,求余数。最后再肯定符号。
public static int[] divide(int a, int b) { //对被除数和除数取绝对值 int A = a < 0 ? add(~a, 1) : a; int B = b < 0 ? add(~b, 1) : b; //对被除数和除数的绝对值求商 int C = A; // 余数C int N = 0; // 商N while (C >= B) { C = minus(C, B); // C-B N = add(N, 1); // N+1 } // 求商的符号 if ((a ^ b) < 0) { N = add(~N, 1); } // 求余数的符合 if (a < 0) { C = add(~C, 1); } return new int[]{N, C}; }
须要指出的是,这种算法在A很大、B很小的状况下效率很低,那该如何优化算法减小while循环的次数呢?
不难想到,除法是由乘法的过程逆推而来的。例如 9÷4=2...1,也就是2*4+1=9。假设用9去减4*2,能够得出结果等于1,由于1小于4,那么就能够得出9÷4的商是2,余数是1。
如何肯定4的倍数是逼近最终结果的关键。咱们知道,int 整型有32位,除首位表示符号位,每一位的大小是 [2^0, 2^1, 2^2, , , 2^30],最大的int整数是2^31-1。因此,咱们能够依次将被除数与2^31, 2^30, ...2^3, 2^2, 2^1, 1相乘,若是除数大于它们的乘积,除数就与之相减,并用相减获得的余数继续做为除数,直到循环结束。
java代码:
public static int[] divide(int a, int b) { // 对被除数和除数取绝对值 int A = a < 0 ? add(~a, 1) : a; int B = b < 0 ? add(~b, 1) : b; int N = 0; // 商 N for (int i = 31; i >= 0; i--) { // 未使用A>=(B<<i)进行判断,由于只有左移B时舍弃的高位不包含1,才至关于该数乘以2的i次方. if ((A >> i) >= B) { // A ÷ 2^i >= B N += (1 << i); // N = N + 2^i A -= (B << i); // A = A - B*2^i } } int C = A; // 余数C // 求商的符号 if ((a ^ b) < 0) { N = add(~N, 1); } // 求余数的符号 if (a < 0) { C = add(~C, 1); } return new int[]{N, C}; }