位运算常见技巧

在新浪微博上看到一篇文章写位运算的写的很深入,文章链接见末尾,特此mark。

0.位运算的种类

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

markdown中|我不知道怎么表示,就用l了


1.判断两个整数是否符号相反


bool oppositeSigns(int x, int y)
{
    bool f = ((x ^ y) < 0); // true iff x and y have opposite signs
    
    return f;
}

2.判断一个数的符号

bool isUnSignInt(int v){
    
    int sign;   // the result goes here
    
    // CHAR_BIT is the number of bits per byte (normally 8).
    sign = -(v < 0);  // if v < 0 then -1, else 0.
    // or, to avoid branching on CPUs with flag registers (IA32):
    sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));
    // or, for one less instruction (but not portable):
    sign = v >> (sizeof(int) * CHAR_BIT - 1);
    
    return sign == 1;
}

3.求一个数的绝对值

int absulteValue(int v){

    unsigned int r;  // the result goes here
    int const mask = v >> (sizeof(int) * CHAR_BIT - 1);
    
    /*explain:在计算机中,负数以其正值的补码形式表达。
     什么叫补码呢?这得从原码,反码说起。
     原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。
     比如 00000000 00000000 00000000 00000101 是 5的 原码。
     反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码。
     取反操作指:原为1,得0;原为0,得1。(1变0; 0变1)
     比如:将00000000 00000000 00000000 00000101每一位取反,得11111111 11111111 11111111 11111010。
     称:11111111 11111111 11111111 11111010 是 00000000 00000000 00000000 00000101 的反码。
     反码是相互的,所以也可称:
     11111111 11111111 11111111 11111010 和 00000000 00000000 00000000 00000101 互为反码。
     补码:反码加1称为补码。
     也就是说,要得到一个数的补码,先得到反码,然后将反码加上1,所得数称为补码。
     比如:00000000 00000000 00000000 00000101 的反码是:11111111 11111111 11111111 11111010。
     那么,补码为:
     11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011
     所以,-5 在计算机中表达为:11111111 11111111 11111111 11111011。转换为十六进制:0xFFFFFFFB。
     
     所以获取一个负数的绝对就是将负数取反在+1即可
     所以最初的形式可写成
     int my_abs(int a)
     {
     int i = a >> 31;
     return i == 0 ? a : (~a + 1);
     }
     
     现在再分析下。对于任何数,与0异或都会保持不变(异或是两个位相同为0,相异位1),与-1即0xFFFFFFFF异或就相当于取反。
     因此,v与mask异或后再减mask(因为mask为0或-1,所以减mask即是要么加0要么加1)也可以得到绝对值。所以可以对上面代码优化下:
     
     r = (v ^ mask) - mask;
     */
    
    
    r = (v + mask) ^ mask;
    
    //or
    //r = (v ^ mask) - mask;

    return r;
}

4.计算最大最小值

int max(int x, int y){
    
    /*
     *利用一点,对于任何数a,因为0的二进制全为0,有a&0 = 0,因为-1的二进制全为1,有a&(-1) = a
     如果x>y,x> (sizeof(int) * CHAR_BIT - 1))); // min(x, y)
     r = x - ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1))); // max(x, y)
     */
    
    return x ^ ((x ^ y) & -(x < y));

}

5.计算一个值是否是2的次幂

bool isPowerOfTwo(int x){
    
    //如果一个数是2的次幂,那么除首位,其余为肯定都为0,减一后其余为为1
    
    bool f = x && !(x & (x - 1));
    
    return f;
}

6.交换

void swap(int &x, int &y){
    
    /*异或运算有两个特性:
    1、一个数异或本身恒等于0,如5^5恒等于0;
    2、一个数异或0恒等于本身,如5^0恒等于5。*/

    if (x != y)
    {
        x ^= y;
        y ^= x;
        x ^= y;
    }
    
}

7.更多技巧,参考文章:http://graphics.stanford.edu/~seander/bithacks.html

后面的内容没那个太深,暂时用到的可能性不太,如果后期有业余精力再去深入研究。

你可能感兴趣的:(位运算常见技巧)