原码、补码和反码
一个数在计算机中的二进制表示形式称为这个数的机器数。机器数是有符号数,机器数的最高位是符号位,0表示0或正数,1表示负数。因为机器数的最高位是符号位,所以机器数的形式值不一定等于真正数值。例如10001010的形式值 138,真正数值 -10。为了加以区分,将机器数 真正数值称为机器数的真值。
原码:原码是机器数的符号位加上机器数的真值的绝对值,最高位是符号位,其余位表示数值。
反码:反码在原码的基础上得到。0和正数的反码与原码相同,负数的反码是将原码的除了符号位之外的每一位取反。
补码:补码是在反码的基础上得到。0和正数的补码与原码、反码相同,负数的补码是在反码的基础上加1得到。
位运算的概念
位运算共有6种,分别是:与、或、异或、取反、左移和右移,其中左移和右移统称移位运算,移位运算又分为算术移位和逻辑移位。上述位运算中,只有取反是一元运算,其余的都是二元运算。
与、或、异或和取反:省略,很简单,不用写。
Tips:计算机中存储的负数为补码。
移位运算:移位运算按照移位方向可以分成左移和右移,按照是否带符号分类可以分成算术移位和逻辑移位。左移运算的符号是<<。高位丢弃,低位补0;右移运算的符号是>>。右移运算时,将全部二进制位向右移动若干位,低位丢弃,高位的补位在算术右移中,高位补最高位,逻辑右移时,高位补0。在C++ ,对于有符号类型,右移运算为算术右移;对于无符号类型,右移运算为逻辑右移。在JAVA中,算术右移是>>,逻辑右移是>>>。
移位运算与乘除法的关系:左移运算对应乘法运算。将一个数左移k位,等价于将这个数乘以2^k。当乘数不是2的整数次幂的时候,可以将乘数拆成若干项2的整数次幂之和,例如,a x 6等价于(a<<2)+(a<<1)。对于任意整数,乘法运算都可以用左移运算实现。算术右移对应除法运算。将一个数右移k位,相当于将这个数除以2^k。例如50右移2位的结果 12,等价于50/4,结果向下取整。对于负数,向更负的方向取整。
位运算的性质:幂等律、交换律、结合律、分配率、德摩根律、取反运算性质、与运算性质、或运算性质、异或运算性质、其他性质:a&(a-1)的结果为将a的二进制表示的最后一个1变成0;a&(-a) (与a&(~(a-1))等价)的结果为只保留a的二进制表示的最后一个1,其余的1都变成0。
一些奇奇怪怪的应用
格雷编码:按阶正向反向加0和1,如[0,1]、[00,01,11,10]、[000,001,011,010,110,111,101,100]
数字范围按位与:神奇的做法
class Solution {
public:
int rangeBitwiseAnd(int left, int right) {
while(left < right){
right = right & (right - 1);
}
return right;
}
};
只出现一次的数字系列:很神奇,cool【https://leetcode-cn.com/leetbook/read/bit-manipulation-and-math/on5yo2/】
状态压缩:状态压缩,顾名思义就是将多个值的状态压缩成一个数字。
位运算用于数学运算
幂运算:判断一个数是不是特定正整数的整数次幂;快速幂。快速幂模板,↓↓
class Solution {
public double myPow(double x, int n) {
long nLong = n;
return nLong >= 0 ? quickMul(x, nLong) : 1.0 / quickMul(x, -nLong);
}
public double quickMul(double x, long n) {
double ans = 1.0;
while (n > 0) {
if (n % 2 == 1) {
ans *= x;
}
x *= x;
n /= 2;
}
return ans;
}
}