位操作的技巧

一,基本概念认知
1, 什么是位运算
程序中的所有数在计算机内存中都是二进制的形式存储的,位运算就是直接对整数在内存中的二进制位进行操作,因为不需要有进制的转换,所以处理速度非常快。

符号运算规则
<<左移若干位,高位丢弃,低位补0
>>右移若干位,对于无符号数,高位补0;有符号数,各编译器处理的不一样,有的补符号位(算术右移),有的补0(逻辑右移)

2,为啥要用补码
计算机中的符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。(来自百度百科)

3,补码的求法:
正数的补码和原码相同;
负数是对应正数原码取反后加1;(或者现将对应正数减1,然后取反)
补码绝对值求法:对补码取反加1;
负数的补码如果一直向右移动一位(相当于/2),则会变成0xFFFFFFFF,形成死循环。因为每次移位负数的最高为都会补上1【视编译器处理情况而定】
注意:-1的十六进制表示为:0xFFFFFFFF; 0X8FFFFFF => MIN_INT(最高位是1)

4, 怎么表示相反数:
n=-n=(n-1)=n+1;正好对应了负数补码的求法;

二,位操作原理和技巧【x既可以表示一个正数,也适用于表示某一位的情况(0或者1)】微笑

1,异或:(三种状态)
x ^ 0 = x;(和0异或没有变化)
x ^ 1s = ~x;(和1异或相当于取反)// 1s = ~0
x ^ x = 0;(和自己异或相当于清0)// interesting and important
x ^ (~x)= 1s;
a ^ b = c => b ^ c = a, b ^ c = a; //swap
a ^ b ^ c = a ^ ( b ^ c ) = (a ^b) ^ c // 结合律

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);

d)清零最低位的1(面试题3.0)

X = X & (X-1)  => 清零最低位的1

(4) 判断奇偶

X & 1 == 1 (X % 2 ==1) // 前者位运算更快,后者部分编译器会优化成位运算

(5) 得到最低位的1

X & -X => 得到最低位的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(绝对值是231)--2147483647(231-1)

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

3.3 乘法运算
乘法的规律是什么呢。看这样的例子:27=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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值