码迷,mamicode.com
首页 > 其他好文 > 详细

位操作的技巧

时间:2015-07-06 23:33:16      阅读:303      评论:0      收藏:0      [点我收藏+]

标签:

一,基本概念认知
1,为啥要用补码
计算机中的符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。(来自百度百科)
2,补码的求法:
正数的补码和原码相同;
负数是对应正数原码取反后加1;(或者现将对应正数减1,然后取反)
补码绝对值求法:对补码取反加1;
负数的补码如果一直向右移动一位(相当于/2),则会变成0xFFFFFFFF,形成死循环。因为每次移位负数的最高为都会补上1.。
3, 怎么表示相反数:
n=-n=~(n-1)=~n+1;正好对应了负数补码的求法;

二,位操作原理和技巧【x既可以表示一个正数,也适用于表示某一位的情况(0或者1)】微笑
1,异或:(三种状态)
x^0=x;(和0异或没有变化)
x^1=~x;(和1异或相当于取反)
x^x=0;(和自己异或相当于清0)
2,&(两种状态:清0或者不变)
x&0=0;(和0取&相当于清0)
x&1=x;(和1取&没有变化)
x&x=x;(和自己取&没有变化)
3,|(两种状态:置1或者不变)
x|0=x;(和0取|没有变化)
x|1=1;(和1取|相当于置1)
x|x=x;(和自己取|没有变化)
4,依据上面几点位操作的应用:
(1) 获取某一位的信息

bool getBit(int num,int i){
  return (num&(1<<i));
}

(2)置位(某一位置1,其他位不影响)

int setBit(int num,int i){
    return num|(1<<i);
 }

(3)清0(虽然异或可以做到,但是取&是最好的办法)
a)只将某一位清0,其他位不动;
利用&特性,和1取&不变,和0取&清0

int clearBit(int num,int i){//清0方法1,使用&
    int mask=~(1<<i);
    return num&mask;
 }

有人想到可以利用异或的特性,和0异或不变,和自己异或清0;但是这样的话,需要判断这一位是0还是1,比较麻烦。

b)将num最高位到i位(包含i位)清零:
和a)类似。把mask变一下。

int mask=(1<<i)-1;

c)将num的i位(含)到0位清零:
只写出mask

int mask=~((i<<(i+1))-1);

三,位操作实现加减乘除
3.0 工具栏
<1>获取整数n的二进制中最后一个1:n&(-n) 或者 n&~(n-1),如:n=010100,则-n=101100,n&(-n)=000100

<2>去掉整数n的二进制中最后一个1:n&(n-1),如:n=010100,n-1=010011,n&(n-1)=010000

例题:求二进制数中1的位数(编程之美)

int count(int num){//不断去除最后一个1,这个写法效率很高
  int res=0;
  while(num){//只循环了1的个数的次数
     num&=(num-1);
     res++;
  }
  return res;
 }

3.1 加运算
对于x和y两个数,如果他们相加过程中完全不需要进位的话,相加是不是就是很容易的事情了。就相当于x^y就OK了吧。
但是如果有进位的情况,就是有那么一个或多个bit,x和y在那一位上都是1。x&y就可以判断哪几位都是1。进位就是左移一位,就是(x&y)<<1;
所以x和y的相加,可以分为两部分:可以看做是不需要进位的那些位之和x^y,加上需要进位的部分之和(x&y)<<1。不断循环即可,直到没有进位。

int add(int x, int y){
    int add,carry;
    do{
        add=x^y;
        carry=(x&y)<<1;
        x=add;
        y=carry;
    }while(carry);
    return add;
}

3.2 减运算
x-y=x+(-y)=x+(~y+1);
int 的范围-2147483648(绝对值是2^31)--2147483647(2^31-1)

int subtract(int x,int y){
     return add(x,add(~y,1));
}

3.3 乘法运算
乘法的规律是什么呢。看这样的例子:2*7=14;
转换为二进制会看的明白:0010*0111=0010*(0001+0010+0100)=0010<<0+0010<<1+0010<<2=0010+0100+1000=1110;
这时候求出7的最后一位1的位置,然后再去掉最后一位1,如此循环到7变为0为止。
考虑溢出,如果大于INT_MAX 输出INT_MAX;如果小于INT_MIN,输出INT_MIN。

int multiply(int x,int y){
     map<unsigned int,int> bit_map;
     bool neg=(x<0)^(y<0);
     long long res=0;
     unsigned int x1=x>0?x:-x;
     unsigned int y1=y>0?y:-y;
     for(int i=0;i<32;i++){
        bit_map[1<<i]=i;
     }
     while(y){
        int lastBit=y1&(-y1);
        res+=x1<<bit_map[lastBit];
        if(res>INT_MAX) return neg==true?INT_MIN:INT_MAX;
        y1&=(y1-1);
     }
     if(neg) res=-res;
     return res;
}

3.4 除法运算
除法就是不断的减去一部分值,对应乘法的加。先减去最大的那个因子。也是根据因子的二进制,先找出因子的最高位,被除数减去这部分,然后找出次高位…直到被除数小于除数。

下面的代码,充分考虑了溢出的情况,针对有INT_MIN输入。如果结果大于INT_MAX,则输出INT_MAX;

int divide(int x,int y){
   assert(y!=0);//y不可以为0;
   bool neg=(x<0)^(y<0);
   unsigned int x1=x>0?x:-x;//防止INT_MIN的溢出
   unsigned int y1=y>0?y:-y;//防止INT_MIN的溢出
   long long pos=y1;//非常关键,防止溢出
   unsigned int res=0;
   int bit=0;
   for(;pos<=x1;bit++)
   {
       pos=pos<<1;
   }
   while(x1>=y1){
     if(x1>=pos){
        res|=1<<bit;
        x1-=pos;
     }else{
         pos>>=1;
         bit--;
     }
   }

   if(res>INT_MAX&&!neg) return INT_MAX;
   if(neg) res=-res;
   return (int)res;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

位操作的技巧

标签:

原文地址:http://blog.csdn.net/mike_learns_to_rock/article/details/46777985

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!