C/C++ 位运算总结

C/C++ 位运算总结

一、基本的位操作运算符

符号 描述 运算规则
& 两个位都为1时,结果才为1
两个位都为0时,结果才为0
^ 异或 两个位相同为0,相异为1
~ 取反 0变1,1变0
<< 左移 各二进位全部左移若干位,高位丢弃,低位补0
>> 右移 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)

注意:

  • 使用位操作算符时尽量添加括号,因为优先级低于一般的算术运算符
  • 只能对整形数据运算

一些基本的等式

~~n = n
-n = ~n + 1 = ~(n - 1)
-~n = n + 1
~-n = n - 1

二、位操作计算技巧

乘/除2的幂次
x >> n; //等价于X / 2^n;
判断奇偶
bool is_odd(int a){
    return a & 1; // 等价于a % 2 == 0;
}
交换两数
void swap(int &a, int &b){ //不使用第3个变量
    a ^= b; //a = old_a ^ old_b
    b ^= a; //b = old_b ^ (old_a ^ old_b) = old_a
    a ^= b; //a = (old_a ^ old_b) ^ old_a = old_b
}
取相反数
int opposite(int a){  
    return ~a + 1; //等价于 a = - a;
} 
取符号

当a>0时候返回1,a<0时返回-1,a=0时返回0

int sign(int a){  
    return !!a - (((unsigned)a >> 31) << 1); 
} 
是否同号
boolean is_same_sign(int x, int y){ //有0的情况例外
    return (x ^ y) >= 0; 
}
取绝对值
int abs1(int a){  
    return (a >> 31) == 0 ? a : (~a + 1);  
}
/* n>>31 取得n的符号,若n为正数,n>>31等于0,若n为负数,n>>31等于-1
若n为正数 n^0=0,数不变,若n为负数有n^-1 需要计算n和-1的补码,然后进行异或运算,
结果n变号并且为n的绝对值减1,再减去-1就是绝对值 */

若右移是算术右移,也可写为

int abs2(int a){  
    int s = a >> 31; //0 或 -1
    return (a ^ s) - s; //s = 0, a ^ 0 - 0 = a, s = 1, a
} 
取最大值

方法1

int max(int a, int b){
    return b & ((a - b) >> 31) | a & (~(a-b) >> 31);
    /*如果a>=b,(a-b)>>31为0,否则为-1*/
}

方法2

int max(int x, int y){
    return x ^ ((x ^ y) & -(x < y));
    /*如果x<y,x<y返回1,否则返回0,与0做与运算结果为0,与-1做与运算结果不变*/
}
取最小值

方法1

int min(int a, int b){
    return a & ((a - b) >> 31) | b & (~(a - b) >> 31);
    /*如果a>=b,(a-b)>>31为0,否则为-1*/
}

方法2

int min(int x, int y){
    return y ^ ((x ^ y) & -(x < y));
    /*如果x<y,x<y返回1,否则返回0,与0做与运算结果为0,与-1做与运算结果不变*/
}
置二进制第 n 位为 1
int set_nth_bit_to_1(int a){
    a = a | (1 << n);
}
置二进制第 n 位为 0
int set_nth_bit_to_0(int a){
    a = a & ~(1 << n);
}
将二进制第 n 位取反
int reverse_nth_bit(int a){
    a = a ^ (1 << n);
}
取二进制最低一位1
int get_low_1(int a){
    return a & (-a); //-a = ~a + 1 = ~(a - 1), eg: a = 01010100 => 00000100
}
取二进制最低一位0
int get_low_0(int a){
    return ~a & (a+1); //eg: a = 01010101 => 00000010
}
取二进制最高一位1
int get_high_1(unsigned int a){//注意是无符号数
    a = a | (a >> 1);
    a = a | (a >> 2);
    a = a | (a >> 4);
    a = a | (a >> 8);
    a = a | (a >> 16);
    return (a + 1) >> 1; //eg: a = 01010101 => 01000000
}
置二进制最低一位1为0
int set_low_1_to_0(int a){
    a = a & (a - 1); //eg: a = 01010100 => 01010000
}
置二进制最低一位0为1
int set_low_0_to_1(int a){
    a = a | (a + 1); //eg: a = 01010101 => 01010111
}
置二进制低位连续的0为1
int set_low_1_to_0(int a){
    a = a | (a - 1); //eg: a = 01010100 => 01010111
}
获得32位int的最大值
int get_int_max(){
    return (1 << 31) - 1;//2147483647
}
获得32位int的最小值
int get_int_min(){
    return (1 << 31); //-2147483648
}
高16位与低16位交换
int exchange_high_low_16(int a){
    a = (a >> 16) | (a << 16);
}
取二进制中1的个数
int count_1(unsigned int a){
    a = (a & 0x55555555) + ((a >> 1) & 0x55555555); 
    a = (a & 0x33333333) + ((a >> 2) & 0x33333333); 
    a = (a & 0x0F0F0F0F) + ((a >> 4) & 0x0F0F0F0F); 
    a = (a & 0x00FF00FF) + ((a >> 8) & 0x00FF00FF); 
    a = (a & 0x0000FFFF) + ((a >> 16) & 0x0000FFFF);
    return a
}
取二进制中前导0的个数
int count_pre_0(unsigned int a){
    if (a == 0) return 32;
    int n = 1;
    if((a >> 16) == 0) {n += 16; a = a <<16;}
    if((a >> 24) == 0) {n += 8; a = a << 8;}
    if((a >> 28) == 0) {n += 4; a = a << 4;}
    if((a >> 30) == 0) {n += 2; a = a << 2;}
    n -= (a >> 31);
    return n;
}
取二进制最低一位1的位数
int Lsb32(unsigned int a){ //LSB = Least Significant Bit
    int n = 31;
    if (a & 0x0000ffff) {n -= 16; a &= 0x0000ffff;}
    if (a & 0x00ff00ff) {n -= 8; a &= 0x00ff00ff;}
    if (a & 0x0f0f0f0f) {n -= 4; a &= 0x0f0f0f0f;}
    if (a & 0x33333333) {n -= 2; a &= 0x33333333;}
    if (a & 0x55555555) n -=1;
    return n;
}
取二进制最高一位1的位数
int Msb32(unsigned int a){ //MSB = Most Significant Bit
    int n = 0;
    if (a & 0xffff0000) {n += 16; a &= 0xffff0000;}
    if (a & 0xff00ff00) {n += 8; a &= 0xff00ff00;}
    if (a & 0xf0f0f0f0) {n += 4; a &= 0xf0f0f0f0;}
    if (a & 0xcccccccc) {n += 2; a &= 0xcccccccc;}
    if (a & 0xaaaaaaaa) n += 1;
    return n;
}
裁剪为0(小于0则返回0)
int clamp_to_0(int x){
    return ((-x) >> 31) & x; //利用带符号的右移
}
裁剪为$2^n-1$(大于$2^n-1$则返回$2^n-1$)
int clamp_to_255(int x){
    return (((255 - x) >> 31) | x) & 255;
}
下一个大于x的$2^n$
unsigned int next_power_of_2(unsigned int x){
    --x;
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;
    return ++x;
}
实现加法
int add(int a, int b) {
    int carry, add;
    do{
        add = a ^ b;
        carry = (a & b) << 1;
        a = add;
        b = carry;
    }while(carry);
    return add;
}
实现减法
int subtract(int a, int b) {
    return add(a, add(~b, 1));
}
实现乘法

乘法可以通过系列移位和加法完成。最后一个1可通过b&~(b-1)求得,可通过b& (b-1)去掉,\

C++写法:可提前计算一个map,高效地得到左移的位数

int multiply(int a, int b) {
    bool neg = (b < 0);
    if(neg) b = -b;
    int sum = 0;
    map<int, int=""> bit_map;
    for(int i = 0; i < 32; i++)
        bit_map.insert(pair<int, int="">(1 << i, i));
    while(b > 0){
        int last_bit = bit_map[b & ~(b - 1)];//最低一位1的位数
        sum += (a << last_bit);
        b &= b - 1;//置最低一位1为0
    }
    return neg ? -sum : sum;
}

C语言写法,调用之前的Lsb32函数

int multiply(int a, int b) {
    int neg = (b < 0);
    if(neg) b = -b;
    int sum = 0;
    while(b > 0){
        int last_bit = Lsb32(b);//最低一位1的位数
        sum += (a << last_bit);
        b &= b - 1;//置最低一位1为0
    }
    return neg ? -sum : sum;
}
实现除法
int divide(int a, int b) {
    bool neg = (a > 0) ^ (b > 0);
    if(a < 0) a = -a;
    if(b < 0) b = -b;
    if(a < b) return 0;
    int msb = 0;
    for(msb = 0; msb < 32; msb++) //也可调用之前的Msb32函数
        if((b << msb) >= a)
            break;
    int q = 0;
    for(int i = msb; i >= 0; i--){
        if((b << i) > a)
            continue;
        q |= (1 << i);
        a -= (b << i);
    }
    return neg ? -q : q;
}

三、位运算压缩数据/作为集合

对于C++,STL提供的bitset类就是位的集合

//初始化
std::bitset<4> foo; //foo: 0000
std::bitset<16> bar (0xfa2); //用整数初始化,bar: 0000111110100010
std::bitset<16> baz (std::string("0101111001")); 
//用字符串初始化,不够长前面补0,baz: 0000000101111001
//添加和删除元素就是位的赋值
foo[1]=1; // foo: 0010
foo[2]=foo[1]; // foo: 0110
foo.set(); // foo: 1111
foo.set(2,0); // foo: 1011
foo.set(2); // foo: 1111
foo.reset(1); // foo: 1101
foo.reset(); // foo: 0000
foo.flip(2); // foo: 0100
foo.flip(); // foo: 1011
//一些统计和判断函数
foo.count(); //Count bits set
foo.size(); //Return size
foo.test(); //Return bit value
foo.any(); //Test if any bit is set
foo.none(); //Test if no bit is set
foo.all(); //Test if all bits are set
//集合操作就位运算
std::bitset<4> foo (std::string("1001"));
std::bitset<4> bar (std::string("0011"));
foo^=bar; // 1010 (XOR,assign)
foo&=bar; // 0010 (AND,assign)
foo|=bar; // 0011 (OR,assign)
foo<<=2; // 1100 (SHL,assign)
foo>>=1; // 0110 (SHR,assign)
~bar; // 1100 (NOT)
bar<<1; // 0110 (SHL)
bar>>1; // 0001 (SHR)
foo==bar; // false (0110==0011)
foo!=bar; // true (0110!=0011)
foo&bar; // 0010
foo|bar; // 0111
foo^bar; // 0101

对于C,可以利用整数

typedef long long set; //char, short, int/long, __int64/long long
//添加
set add(set s, int i){
    s |= (1 << i);
    return s;
}
//删除
set delete(set s, int i){
    s &= ~(1 << i);
    return s;
}
//在集合内
int in(set s, int i){
    return s & (1 << i);
}
//并集
set union(set s1, set s2){
    return s1 | s2;
}
//交集
set intersect(set s1, set s2){
    return s1 & s2;
}
//差集, s1 - s2
set except(set s1, set s2){
    return s1 & ~s2;
}

四、参考

http://www.cplusplus.com/reference/bitset/bitset/

http://graphics.stanford.edu/~seander/bithacks.html#OperationCounting

https://blog.whezh.com/bit-hacks/

http://www.matrix67.com/blog/archives/263

https://www.clarkok.com/blog/2015/06/13/%E5%8F%96%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%9C%80%E9%AB%98%E4%BD%8D%E7%9A%84%E4%B8%80/

https://blog.csdn.net/zmazon/article/details/8262185

https://www.zhihu.com/question/38206659

https://blog.csdn.net/MoreWindows/article/details/7354571

http://lijinma.com/blog/2014/05/29/amazing-xor/

文章目录
  1. C/C++ 位运算总结
    1. 一、基本的位操作运算符
    2. 二、位操作计算技巧
      1. 乘/除2的幂次
      2. 判断奇偶
      3. 交换两数
      4. 取相反数
      5. 取符号
      6. 是否同号
      7. 取绝对值
      8. 取最大值
      9. 取最小值
      10. 置二进制第 n 位为 1
      11. 置二进制第 n 位为 0
      12. 将二进制第 n 位取反
      13. 取二进制最低一位1
      14. 取二进制最低一位0
      15. 取二进制最高一位1
      16. 置二进制最低一位1为0
      17. 置二进制最低一位0为1
      18. 置二进制低位连续的0为1
      19. 获得32位int的最大值
      20. 获得32位int的最小值
      21. 高16位与低16位交换
      22. 取二进制中1的个数
      23. 取二进制中前导0的个数
      24. 取二进制最低一位1的位数
      25. 取二进制最高一位1的位数
      26. 裁剪为0(小于0则返回0)
      27. 裁剪为$2^n-1$(大于$2^n-1$则返回$2^n-1$)
      28. 下一个大于x的$2^n$
      29. 实现加法
      30. 实现减法
      31. 实现乘法
      32. 实现除法
    3. 三、位运算压缩数据/作为集合
    4. 四、参考
|