位运算是计算机的核心基础,数据的表示和计算几乎都少不了。所以我们深入学习下位运算。
数字在计算机中的表示
机器数:一个数在计算机中的二进制表示形式,叫做这个数的机器数。并且这个数是带符号的,0表示正数,1表示负数。
真值:机器数的第一位表示符号位,剩余位的值转化为十进制,就是真值。
原码:第一位为符号位,剩余位数表示值。
反码:正数的反码与原码一致;负数的反码就是符号位不变,其余各位取反。
补码:正数的补码与原码一致;负数的补码就是符号位不变,其余各位取反,最后加1。
位运算规则
位运算主要有:与、或、异或、取反、左移、右移,其中左移和右移统称为移位运算,移位运算又分为算术移位和逻辑移位。
与:符号& 两个数对应的位都为1时,结果为1,否则为0;
或:符号 | ,两个数对应的位都为0时,结果为0,否则为1;
异或:符号⊕(在代码中用^表示),两个数对应的位不相等时,结果为1,否则为0;
取反:符号~,对一个数的每个二进制位进行取反操作
移位运算
移位运算按照移位方向分为左移和右移,按照是否带符号分类可以分为算术移位和逻辑移位。
左移运算:符号 <<,左移运算时,将全部二进制向左移动若干位,高位丢弃,低位补0(除负数反码:补1);
右移运算:符号 >>, 右移运算时,将全部二进制向右移动若干位,低位丢弃,高位的补位由算术移位或逻辑移位决定:
算术右移:
正数:
原码、补码、反码:补0
负数:
原码:补0;
补码:补1;
反码:补1;
逻辑右移:高位补0;
在Java中,所有的表示整数的类型都是有符号类型,所以要区分算术右移和逻辑右移。在Java中,算术右移的符号是 >>,逻辑右移的符号是 >>>。
移位运算和乘除法的关系
左移运算对应乘法运算。将一个数左移k位,等价于将这个数乘以2^k。当乘数不是2的整次幂时,可以将乘数拆成若干项2的整数次幂的和。对于任意整数,乘法运算都可以用左移运算实现,但是需要注意溢出情况。
算术右移运算对应除法运算,将一个数右移k位,相当于将这个数除以2^k。
位运算常用技巧
假设以下出现的变量都是有符号整数。
幂等律:a & a = a,a | a = a(异或不满足幂等律);
交换律:a & b = b & a,a | b = b | a,a ⊕ b = b ⊕ a;
结合律:(a & b)& c = a &(b & c),(a | b)| c = a |(b | c),(a ⊕ b)⊕ c = a ⊕(b ⊕ c);
分配律:(a & b)| c =(a | c)&(b | c),(a | b)& c =(a & c)|(b & c),(a ⊕ b)& c = (a & c)⊕(b & c);
德摩根律:~(a & b)= (~a)|(~b),~(a | b)= (~a)&(~b);
取反运算性质:-1 =~0,-a = ~(a - 1);
与运算性质:a & 0 = 0,a & (-1)= a,a & (~a)= 0;
或运算性质:a | 0 = a;
异或运算性质:a ⊕ 0 = a, a ⊕ a = 0;
获取
该方法是将1左移 i 位,得到形如00010000的值,接着堆这个值与num执行“位与”操作,从而将 i 位之外的所有位清零,最后检查该结果是否为零。不为零说明 i 位为1,否则 i 位为0。
boolean getBit (int num,int i){
return ((num & (1 << i)) != 0);
}
设置(将某个值设置为1)
setBit先将1左移 i 位,得到形如00010000的值,接着堆这个值和num执行“位或”操作,这样只会改变 i 位的数据。这样除 i 位外位的位均为零,故不会影响num的其余位。
int setBit (int num,int i){
return num | (1 << i);
}
清零(将某一位置设置为0)
该方法与setBit相反,首先将1左移 i 位获得形如00010000的值,对这个值取反进而得到类似11101111的值,接着对该值和num执行“位与”,故不会影响到num的其余位,只会清零 i 位。
int clearBit(int num,int i){
int mark = ~(1 << i);
return num & mark;
}
更新
这个方法是将setBit和clearBit合二为一,首先用诸如11101111的值将num的第 i 为清零。接着将带写入值v左移 i 位,得到一个 i 位为v但其余位都为0的数,最后对之前的结果执行“位或”操作,v为1这num的 i 位更新为1,否则为0;
int updateBit(int num,int i,int v){
int mark = ~(1 << i);
return (num & mark) | (v << i);
}