位运算的小技巧
【n & 1 == 0 ? 偶数:奇数】可以用来判断是奇数还是偶数
左移运算a<<1可以作为乘2使用
右移运算a>>1可以作为除以2使用
获得第 i 位【(a>>i)&1】或者【 a&(1<<i)】
将第 i 位设为1【a=a|(1<<i)】
将第 i 位设为0【a=a&(~(1<<i))】
将第 i 位取反 【a=a^(1<<i)】
当b是2的幂次的时候, a & (b-1) == a % b
判断x是否是2的幂【x & (x-1) == 0 ? 是:否】 【x & (-x) == x ? 是:否】
获取最右侧的1,二进制中最右侧的1的位置为 1,且其它位全部为 0。【x&(-x)】
去除最右侧的1,将最右边的 1 设置为 0:【x & (x - 1)】
位运算的基础
不使用比较判断找出较大值
1 //参数n不是0 就是1 2 // 1>0 0>1 实现1变0,0变1 3 public static int flip(int n){ 4 return n ^ 1; 5 } 6 //n是负数 返回0 7 //n是非负数 返回1 8 public static int sign(int n){ 9 return flip((n >> 31) & 1); 10 //n>>31是把符号位移到最后, 11 // 如果是负数 ******1&00000001 = 1 -> flip -> 0 12 // 如果是正数 ******0&00000001 = 0 -> flip -> 1 13 } 14 15 public static int getMax(int a, int b){ 16 int c = a-b; 17 int signA = sign(c);//如果a>=b signC = 1; a<b signC = 0 18 int signB = flip(signA);//signA反转 19 return signA * a + signB * b; 20 }
上述代码存在问题:a-b可能会溢出
1 public static int getMax2(int a, int b){ 2 int sa = sign(a); 3 int sb = sign(b); 4 int sc = sign(a-b); 5 int difSab = sa ^ sb; //ab符号是否一样 一样 0 不一样 1 6 int sameSab = flip(difSab);// 一样 1 不一样 0 7 //如果a和b的符号相同 肯定不会溢出 8 9 //在a和b符号相同的情况下 a-b>=0; 返回A【sameSab * sc】 10 //在a和b符号不同的情况下可能溢出,但如果A大于0,可直接返回【difSab * sa】 11 int returnA = sameSab * sc + difSab * sa; 12 //A B互斥 直接反转 13 int returnB = flip(returnA); 14 return a * returnA + b * returnB; 15 16 }
上述代码解决了溢出问题。return的部分保证returnA和returnB互斥,只会返回a或b
判断32位正数是不是2的幂,4的幂
2的幂:2进制状态中只有1个1【充要条件】
获取最右侧的1:x&(-x)
【二进制中最右边的 1,且其它位全部为 0。】
x & (-x) == x 那么x就是2的幂
去除最右侧的1:x & (x - 1)
【最右边的 1 设置为 0】
(x - 1)代表了将 x 最右边的 1 设置为 0,并且将较低位设置为 1。
如果 x & (x - 1) == 0 那么 x就是2的幂【因为它只有1位是1】
4的幂:4的幂一定是2的幂,所以4的幂也只有1个1【必要不充分条件】
4的幂的1一定在 1位、3位、5位、7位....上(奇数位)
32位 0x55555555 = 0101010101....0101(奇数位上全是1)
所以只要x & 010101.....010101 != 0 则是4的幂
1 public static boolean isTowPower(int x){ 2 return (x & -x) == x;//取出1 3 //return (x & (x-1)) == 0;//去掉1 4 } 5 public static boolean isFourPower(int x){ 6 return isTowPower(x) && (x & 0x55555555)!=0; 7 }
不使用算数运算符实现a和b的加减乘除
加法
前提:a和b运算后的结果一定不会溢出。
异或:无进位相加(不包含进位信息的加法结果)
进位信息:&之后左移1位
12 + 6
01100
00110
^--------
01010
&-------
00100
<<------
01000
异或结果+进位信息 直到不再产生进位信息
01010
01000
^-------
00010
&------
01000
<<-----
10000
异或结果+进位信息 直到不再产生进位信息
00010
10000
^-------
10010
&------
00000
得到结果:10010 = 2+16 = 18
1 public static int add(int a, int b){ 2 int sum = a; 3 while(b!=0){ 4 sum = a ^ b;//无进位相加 5 b = (a & b) << 1;//进位信息 6 a = sum;//无进位相加+进位信息 直到 进位信息==0 7 } 8 return sum; 9 }
减法
a - b = a + (-b)
相反数:一个数取反+1,就是这个数的相反数(求负数补码的方法)
public static int sub(int a,int b){ return add(a,add(~b,1)); }
乘法
被乘数 X 乘数 =
对于乘数的每一位,如果是 0 :00000000左移1位
如果是1:被乘数左移1位(在上一次的基础上左移)
+--------------------------------------------------------------------
得到结果
00001000
00000110
------
00010000
00000011
------
add (00010000+0=00010000)
00100000
00000001
add (00100000+00010000=00110000)
------
01000000
00000000
返回00110000=32+16 = 48 √
public static int multi(int a, int b){ int res = 0; while (b != 0){ if((b & 1) != 0){ res = add(res, a); } a <<= 1; b >>>= 1;//无符号右移用0补位 } return res; }
除法
a / b
将b左移到可以被a减去的最大值(即:b左移后 b' <= a)
a = a - b' 直到 a - b' = 0
这个过程只适用于a >= 0 && b >= 0的情况
所以需要把a和b都转成正数,计算完后再判断res的真实符号
1 public static int div(int a, int b){ 2 int x = a > 0 ? a:add(~a,1); 3 int y = b > 0 ? b:add(~b,1); 4 int res = 0; 5 for(int i = 31; i > -1; i = sub(i,1)){ 6 if((x >> i) >= y){ 7 res |= (1<<i); 8 x = sub(x, y << i); 9 } 10 } 11 return isNeg(a) ^ isNeg(b) ? add(~res,1):res; 12 }