`

位运算二进制大杂烩一劳永逸

阅读更多

先交代下位运算的基础知识

 

a & b 

a | b 

a ^ b  

  ~a  

a << b

a >> b 

 

位运算应用口诀

清零取反要用与,某位置一可用或,

若要用反和交换,轻轻松松用异或。

 

移位运算

要点 1 它们都是双目运算符,两个运算分量都是整形,结果也是整形。

         2 "<<" 左移:右边空出的位上补0,左边的位将从字头挤掉,其值相当于乘2。

         3 ">>"右移:右边的位被挤掉。对于左边移出的空位,如果是正数则空位补0,若为负数,可能补0或补1,这取决于所用的计算机系统和整数有无符号,可以查看http://dsqiu.iteye.com/blog/1687944 可以找到说明。

         4 ">>>"运算符,右边的位被挤掉,对于左边移出的空位一概补上0。

 

二进制补码运算公式:   

  -x   =   ~x   +   1   =   ~(x-1)   

   ~x   =   -x-1     

  -(~x)   =   x+1   

 ~(-x)   =   x-1   

  x+y   =   x   -   ~y   -   1   =   (x|y)+(x&y)     

  x-y   =   x   +   ~y   +   1   =   (x|~y)-(~x&y)     

  x^y   =   (x|y)-(x&y)   

  x|y   =   (x&~y)+y   

  x&y   =   (~x|y)-~x   

    

  x==y:         ~(x-y|y-x)   

  x!=y:         x-y|y-x   

  x<   y:         (x-y)^((x^y)&((x-y)^x))   

  x<=y:         (x|~y)&((x^y)|~(y-x))   

  x<   y:         (~x&y)|((~x|y)&(x-y))//无符号x,y比较   

  x<=y:         (~x|y)&((x^y)|~(y-x))//无符号x,y比较   

 

应用举例

(1) 判断int型变量a是奇数还是偶数           

       a&1   = 0 偶数

       a&1 =   1 奇数

(2) 取int型变量a的第k位 (k=0,1,2……sizeof(int)),即a>>k&1

(3) 将int型变量a的第k位清0,即a=a&~(1<<k) 

(4) 将int型变量a的第k位置1, 即a=a|(1<<k)

(5) int型变量循环左移k次,即a=a<<k|a>>sizeof(int)-k   

(6) int型变量a循环右移k次,即a=a>>k|a<<sizeof(int)-k   

(7)整数的平均值

对于两个整数x,y,如果用 (x+y)/2 求平均值,会产生溢出,因为 x+y 可能会大于INT_MAX,但是我们知道它们的平均值是肯定不会溢出的,我们用如下算法:

int average(int x, int y)   //返回X,Y 的平均值

{   

     return (x&y)+((x^y)>>1);

}

x+y= x&y>>1+x^y;

(8)判断一个整数是不是2的幂,对于一个数 x >= 0,判断他是不是2的幂

boolean power2(int x)

{

    return ((x&(x-1))==0)&&(x!=0);

}

(9)不用temp交换两个整数

void swap(int x , int y)

{

    x ^= y;

    y ^= x;

    x ^= y;

}

y=y^y^x;y^y=0;0^x=x;

(10)计算绝对值

int abs( int x )

{

int y ;

y = x >> 31 ;

return (x^y)-y ;        //or: (x+y)^y

}

(11)取模运算转化成位运算 (在不产生溢出的情况下)

         a % (2^n) 等价于 a & (2^n - 1)

(12)乘法运算转化成位运算 (在不产生溢出的情况下)

         a * (2^n) 等价于 a<< n

(13)除法运算转化成位运算 (在不产生溢出的情况下)

         a / (2^n) 等价于 a>> n

        例: 12/8 == 12>>3

(14) a % 2 等价于 a & 1       

(15) if (x == a) x= b;

            else x= a;

        等价于 x= a ^ b ^ x;  //这里是有问题的:x=a^b^x;只有当x=b时才有x=a;

(16) x 的 相反数 表示为 (~x+1)

(17)把最后一位变成1       | (101100->101101)          | x | 1

(18)把最后一位变成0       | (101101->101100)          | x | 1-1

(19)最后一位取反          | (101101->101100)          | x ^ 1

(20)右数第k位取反         | (101001->101101,k=3)      | x ^ (1 << (k-1))

(21)取末k位               | (1101101->1101,k=5)       | x  & (1 << k-1)

(22)取右数第k位           | (1101101->1,k=4)          | x >> (k-1) & 1

(23)把末k位变成1          | (101001->101111,k=4)      | x | (1 << k-1)

(24)末k位取反             | (101001->100110,k=4)      | x ^ (1 << k-1)

(25)把右边连续的1变成0    | (100101111->100100000)    | x & (x+1)

(26)把右起第一个0变成1    | (100101111->100111111)    | x | (x+1)

(27)把右边连续的0变成1    | (11011000->11011111)      | x | (x-1)

(28)取右边连续的1         | (100101111->1111)         | (x ^ (x+1)) >> 1

(29)去掉右起第一个1的左边 | (100101000->1000)         | x & (x ^ (x-1))

对连续的处理,就是要知道连续的位数,再做操作即可。

╝②

  符号函数:sign(x)   =   -1,   x<0;   0,   x   ==   0   ;   1,   x   >   0   
  int   sign(int   x)   
  {   
  return   (x>>31)   |   (unsigned(-x))>>31   ;//x=-2^31时失败(^为幂)   
  }   
    
  三值比较:cmp(x,y)   =   -1,   x<y;   0,   x==y;   1,   x   >   y   
  int   cmp(   int   x,   int   y   )   
  {   
  return   (x>y)-(x-y)   ;   
  }   
    
  doz=x-y,   x>=y;   0,   x<y   
  int   doz(int   x,   int   y   )   
  {   
  int   d   ;   
  d   =   x-y   ;   
  return   d   &   ((~(d^((x^y)&(d^x))))>>31)   ;   
  }   
    
  int   max(int   x,   int   y   )     
  {   
  int   m   ;   
  m   =   (x-y)>>31   ;     
  return   y   &   m   |   x   &   ~m   ;   
  }   
    
  不使用第三方交换x,y:   
  1.x   ^=   y   ;   y   ^=   x   ;   x   ^=   y   ;   
  2.x   =   x+y   ;   y   =   x-y   ;   x   =   x-y   ;   
  3.x   =   x-y   ;   y   =   y+x   ;   x   =   y-x   ;   
  4.x   =   y-x   ;   x   =   y-x   ;   x   =   x+y   ;     
       
  下舍入到2的k次方的倍数:   
  1.x   &   ((-1)<<k)   
  2.(((unsigned)x)>>k)<<k   
  上舍入:   
  1.   t   =   (1<<k)-1   ;   x   =   (x+t)&~t   ;   
  2.t   =   (-1)<<k   ;   x   =   (x-t-1)&t   ;   
    
  位计数,统计1位的数量:   
  1.   
  int   pop(unsigned   x)   
  {   
  x   =   x-((x>>1)&0x55555555)   ;   
  x   =   (x&0x33333333)   +   ((x>>2)   &   0x33333333   )   ;   
  x   =   (x+(x>>4))   &   0x0f0f0f0f   ;   
  x   =   x   +   (x>>8)   ;   
  x   =   x   +   (x>>16)   ;   
  return   x   &   0x0000003f   ;   
  }   
  2.   
  int   pop(unsigned   x)   {   
  static   char   table[256]   =   {   0,1,1,2,   1,2,2,3,   ....,   6,7,7,8   }   ;   
  return   table[x&0xff]+table[(x>>8)&0xff]+table[(x>>16)&0xff]+table[(x>>24)]   ;   
  }   
    
  奇偶性计算:   
  x   =   x   ^   (   x>>1   )   ;   
  x   =   x   ^   (   x>>2   )   ;   
  x   =   x   ^   (   x>>4   )   ;   
  x   =   x   ^   (   x>>8   )   ;   
  x   =   x   ^   (   x>>16   )   ;   
  结果中位于x最低位,对无符号x,结果的第i位是原数第i位到最左侧位的奇偶性   
       
  位反转:   
  unsigned   rev(unsigned   x)   
  {   
  x   =   (x   &   0x55555555)   <<   1   |   (x>>1)   &   0x55555555   ;   
  x   =   (x   &   0x33333333)   <<   2   |   (x>>2)   &   0x33333333   ;   
  x   =   (x   &   0x0f0f0f0f)   <<   4   |   (x>>4)   &   0x0f0f0f0f   ;   
  x   =   (x<<24)   |   ((x&0xff00)<<8)   |   ((x>>8)   &   0xff00)   |   (x>>24)   ;   
  return   x   ;   
  }   
    
  递增位反转后的数:   
  unsigned   inc_r(unsigned   x)   
  {   
  unsigned   m   =   0x80000000   ;   
  x   ^=   m   ;   
  if(   (int)x   >=   0   )     
  do   {   m   >>=   1   ;   x   ^=   m   ;   }   while(   x   <   m   )   ;   
  return   x   ;   
  }   
    
  混选位:   
  abcd   efgh   ijkl   mnop   ABCD   EFGH   IJKL   MNOP->aAbB   cCdD   eEfF   gGhH   iIjJ   kKlL   mMnN   oOpP   
  unsigned   ps(unsigned   x)   
  {   
  unsigned   t   ;   
  t   =   (x   ^   (x>>8))   &   0x0000ff00;   x   =   x   ^   t   ^   (t<<8)   ;   
  t   =   (x   ^   (x>>4))   &   0x00f000f0;   x   =   x   ^   t   ^   (t<<4)   ;   
  t   =   (x   ^   (x>>2))   &   0x0c0c0c0c;   x   =   x   ^   t   ^   (t<<2)   ;   
  t   =   (x   ^   (x>>1))   &   0x22222222;   x   =   x   ^   t   ^   (t<<1)   ;   
  return   x   ;   
  }   
    
  位压缩:   
  选择并右移字x中对应于掩码m的1位的位,如:compress(abcdefgh,01010101)=0000bdfh   
  compress_left(x,m)操作与此类似,但结果位在左边:   bdfh0000.   
  unsigned   compress(unsigned   x,   unsigned   m)   
  {   
  unsigned   mk,   mp,   mv,   t   ;   
  int   i   ;   
    
  x   &=   m   ;   
  mk   =   ~m   <<   1   ;   
  for(   i   =   0   ;   i   <   5   ;   ++i   )   {   
  mp   =   mk   ^   (   mk   <<   1)   ;   
  mp   ^=   (   mp   <<   2   )   ;   
  mp   ^=   (   mp   <<   4   )   ;   
  mp   ^=   (   mp   <<   8   )   ;   
  mp   ^=   (   mp   <<   16   )   ;   
  mv   =   mp   &   m   ;   
  m   =   m   ^   mv   |   (mv   >>   (1<<i)   )   ;   
  t   =   x   &   mv   ;   
  x     =   x   ^   t   |   (   t   >>   (   1<<i)   )   ;   
  mk   =   mk   &   ~mp   ;   
  }   
  return   x   ;   
  }     
  位置换:   
  用32个5位数表示从最低位开始的位的目标位置,结果是一个32*5的位矩阵,   
  将该矩阵沿次对角线转置后用5个32位字p[5]存放。   
  SAG(x,m)   =   compress_left(x,m)   |   compress(x,~m)   ;   
  准备工作:   
  void   init(   unsigned   *p   )   {   
  p[1]   =   SAG(   p[1],   p[0]   )   ;   
  p[2]   =   SAG(   SAG(   p[2],   p[0]),   p[1]   )   ;   
  p[3]   =   SAG(   SAG(   SAG(   p[3],   p[0]   ),   p[1]),   p[2]   )   ;   
  p[4]   =   SAG(   SAG(   SAG(   SAG(   p[4],   p[0]   ),   p[1])   ,p[2]),   p[3]   )   ;   
  }   
  实际置换:   
  int   rep(   unsigned   x   )   {   
  x   =   SAG(x,p[0]);   
  x   =   SAG(x,p[1]);   
  x   =   SAG(x,p[2]);   
  x   =   SAG(x,p[3]);   
  x   =   SAG(x,p[4]);   
  return   x   ;   
  }   
    
  二进制码到GRAY码的转换:   
  unsigned   B2G(unsigned   B   )   
  {   
  return   B   ^   (B>>1)   ;   
  }   
  GRAY码到二进制码:   
  unsigned   G2B(unsigned   G)   
  {   
  unsigned   B   ;   
  B   =   G   ^   (G>>1)   ;   
  B   =   G   ^   (G>>2)   ;   
  B   =   G   ^   (G>>4)   ;   
  B   =   G   ^   (G>>8)   ;   
  B   =   G   ^   (G>>16)   ;   
  return   B   ;   
  }   
    
  找出最左0字节的位置:   
  int   zbytel(   unsigned   x   )   
  {   
  static   cahr   table[16]   =   {   4,3,2,2,   1,1,1,1,   0,0,0,0,   0,0,0,0   }   ;   
  unsigned   y   ;   
  y   =   (x&0x7f7f7f7f)   +   0x7f7f7f7f   ;   
  y   =   ~(y|x|0x7f7f7f7f)   ;   
  return   table[y*0x00204081   >>   28]   ;//乘法可用移位和加完成   
  }   

交换C++对象
template <typename T>
void swap(T& obj1, T& obj2)
{
        const int sizeOfObj = sizeof(T);
        char* pt1 = (char*)&obj1;
        char* pt2 = (char*)&obj2;

        for (size_t i=0; i<sizeOfObj; i++)
        {
                pt1[i] ^= pt2[i];
                pt2[i] ^= pt1[i];
                pt1[i] ^= pt2[i];
        }
}

查看对象在内存中的位值
string bitsOfUChar(unsigned char c)
{
        const int numOfBitsInUChar = 8;
        unsigned int mask = (1<<7);
        string result(8, '0');

        for (size_t i=0; i<numOfBitsInUChar; i++)
        {
                if ( mask & c)
                        result[i] = '1';

                mask >>= 1;
        }

        return result;
}

template <typename T>
string bitsInMemory(const T& obj)
{
        int sizeOfObj = sizeof(obj);
        unsigned char* pt = (unsigned char*)&obj;
        string result;

        for (size_t i=0; i<sizeOfObj; i++)
        {
                result += bitsOfUChar(pt[i]);
                result += ' ';
        }

        return result;
}

比如bitsInMemory(12),会输出00001100 00000000 00000000 00000000, 我就知道我自己的机器是小尾顺序的了。

╝③

题目:1.二进制1的个数,2.前导0的个数,3.二进制逆序,4.二进制循环移位,5.高低位交换,6.当然还有很多变形题

二进制1的个数

 

计算二进制1的个数,位运算可以有多种方法,本文实现了三种方法,前两种思想基本一样,细节不同,可对比看一下,欢迎讨论,代码如下

#include<stdio.h>
 
int bitcount_1(unsigned x); // 移位 n 次
int bitcount_2(unsigned x); // 移位 <= n 次
int bitcount_3(unsigned x); // 移位 count 次
void displaybits(int x); // 打印二进制
 
void main()
{
    int y = 521;
    displaybits(y);
    printf("%d\n",bitcount_3(y));
}
int bitcount_1(unsigned x)
{
    int count = 0;
    for(unsigned i = 1 << 31; i > 0; i >>= 1)//屏蔽码一般使用unsigned
    {
        if(x & i)
        {
            ++count;
        }
    }
    return count;
}
 
int bitcount_2(unsigned x)
{
    int count = 0;
    for(; x > 0; x >>= 1)
    {
        if(x & 1)
        {
            ++count;
        }
    }
    return count;
}
 
int bitcount_3(unsigned x)
{
    int count = 0;
    for(; x != 0; x &= (x-1))
    {
        ++count;
    }
    return count;
}
 
void displaybits(int x)
{
    for(unsigned i = (1 << 31); i > 0; i >>= 1) //屏蔽码一般使用unsigned
    {
        printf("%c",x & i ? '1' : '0');
    }
    printf("\n");
}

  前导0的个数

 

我的做法就是简单的使用屏蔽码自左向右做一个遍历计数,可参照上题打印二进制验证,这里不罗列测试程序。

 

int prezeroCount(int x)
{
    int count = 0;
    unsigned mask = 1 << 31; //屏蔽码一般使用unsigned
    while(0 == (x & mask))
    {
        ++count;
        x <<= 1;
    }
    return count;
}

二进制逆序

我的想法是从右到左取位存在字符数组中,然后打印即可#include<stdio.h>

 

 
#define INTBIT 32
 
void bitreverse(char *s, int x);
 
void main()
{
    int x = 1314520; //逆序为00011011011100000010100000000000
    char s[INTBIT+1] = {0};
    bitreverse(s,x);
    printf("%s\n",s);
 
}
 
void bitreverse(char *s, int x)
{
    int i = 0;
    for(; i < INTBIT; x >>= 1)
    {
        s[i++] = x & 1 ? '1' : '0';
    }
    s[i] = '\0';
}

 二进制循环移位

 

编写一个函数shift(x,n),该函数返回将x循环右移(即从最右端移出的位将从最左端移入)n(二进制)位后所得到的值。

该题的解决思路其实就类似于《编程之美》中“数组循环移位”的思想,这里不再罗列分析。

根据位运算的不同变换,我提供了两种实现,其中第二种实现比较直观简单,欢迎讨论。 

#include<stdio.h>
 
#define INTSEPR 32
 
void displaybits(int x);
void shift_1(int &x, int n);
void shift_2(int &x, int n);
 
void main()
{
    int x = 52, y = 52;
    displaybits(x);
 
    shift_1(x,13);
    displaybits(x);
 
    shift_2(y,13);
    displaybits(y);
}
 
void displaybits(int x)
{
    for(unsigned i = (1 << 31); i > 0; i >>= 1)
    {
        printf("%c",x & i ? '1' : '0');
    }
    printf("\n");
}
 
void shift_1(int &x, int n)
{
    n %= INTSEPR;
    unsigned i = (x & ~(~0<<n)) << (INTSEPR-n);
    unsigned j = x >> n;
    x = i | j;
}
 
void shift_2(int &x, int n)
{
    n &= 0x1f; // 对32取余的另一种方式
    x = (x << (INTSEPR-n)) | (x >> n);
}

高低位交换

高低位交换便是循环移位的简单版本,用上题的方法一句话便可实现。

 

x = (x << 16) | (x >> 16);

 ╝①

 

 

小结

看到这里应该对二进制或者位运算基本的运算和变形都没有问题,还包括了高级运算的位运算的求解,更有涉及C++类的相关操作,希望能对位运算能了然于心。如果你有任何建议或者批评和补充,请留言指出,不胜感激,更多参考请移步互联网。

 

 

 

 

 

 

参考:

①勇幸|Thinking:http://www.ahathinking.com/archives/78.html

②Matrix67:http://www.matrix67.com/blog/

漫步者×&……%¥http://www.cppblog.com/Walker/articles/80466.html

icejoywoo's place:http://www.cnblogs.com/icejoywoo/archive/2012/03/08/2385338.html

分享到:
评论

相关推荐

    四位二进制ALU运算器

    四位二进制ALU运算器设计与实现 本设计是数字系统综合设计的一部分,旨在实现四位二进制数的逻辑运算和算术运算。ALU( Arithmetic Logic Unit)是计算机系统中最基本的组成部分之一,负责执行算术运算和逻辑运算。...

    C++ 二进制运算

    本文将深入探讨C++中的二进制运算,包括按位与、按位或、按位异或、按位取反以及移位运算等核心操作,并通过具体实例来阐述它们的工作原理及应用场景。 #### 按位与运算 (&) 按位与运算符`&`用于比较两个数的二...

    位运算实现十进制转换为二进制

    在本主题中,我们将深入探讨如何使用位运算将十进制数转换为二进制数。位运算主要包括与(&)、或(|)、非(~)、异或(^)以及左移()和右移(&gt;&gt;)等操作。 首先,我们要理解十进制和二进制之间的关系。十进制是...

    java二进制运算器(加、见、乘、除)

    在Java中,可以将每个二进制位视为一个独立的乘法运算,然后将结果组合起来。 5. **二进制除法**: 二进制除法稍微复杂一些,通常涉及多次右移(&gt;&gt;&gt;运算符)和与运算(&)来模拟长除法的过程。每次右移相当于除以2,...

    Multisim八位二进制转三位十进制

    《Multisim八位二进制转三位十进制——深入理解数字电路设计与仿真》 在电子工程和计算机科学领域,数据的表示和转换是基础且关键的一部分。本篇文章将详细探讨如何利用Multisim软件进行八位二进制到三位十进制的...

    二进制逻辑运算.pdf

    逻辑运算与传统的算术运算不同,它不是对数值的加减乘除,而是对二进制位的处理。逻辑运算主要关注位与位之间的关系,不涉及进位或借位。逻辑运算包括四种基本类型:逻辑加法(或运算)、逻辑乘法(与运算)、逻辑...

    bc.zip_位运算_进制转换

    位运算指的是对二进制数进行的基本操作,如与(AND)、或(OR)、非(NOT)、异或(XOR)以及左移(LSHIFT)、右移(RSHIFT)。这些操作在低级别编程中用于高效地处理数据和实现特定逻辑。 1. 位运算: - **与...

    二进制位操作演示小工具

    二进制位操作是计算机科学中的基础概念,它在编程中扮演着至关重要的角色,特别是在低级别语言如C和C++以及嵌入式系统中。这个"二进制位操作演示小工具"是为了帮助用户直观地理解并实践这些操作而设计的。下面,我们...

    大数运算包含加,减,乘,除,取模,幂运算,模幂运算。支持十进制运算,二进制运算.zip

    在二进制环境下,幂运算可能涉及位操作和位移。 6. **模幂运算**:这是大数运算中的特殊形式,即求a的b次方对c取模的结果。模幂运算是RSA算法的关键部分,也广泛用于其他加密和数论问题。 7. **十进制与二进制运算...

    C++位运算与进制转换小工具,方便C、C++学习过程中对位的处理,方便观察十六进制地址的变化

    于是我就开发这个集“位”的各种运算与进制转换的小工具,对于学习C及C++起了很大的帮助,再也不用打开计算器来一个个算了。非常方便,非常人性。 本小工具具备以下功能: 一.位运算相关功能: 1.位与运算 2.位或...

    易语言二进制递归运算

    二进制递归运算通常涉及到计算机科学中的基本计算方法,特别是涉及到二进制数据的处理,而递归则是一种解决问题的方法,它通过调用自身来解决更小规模的问题,直到达到某个基础情况。 在易语言中,进行二进制递归...

    位运算&进制转换工具

    本软件集合了位运算跟进制的转换,简单操作,方便软件工程人员使用,同时也适用于初学c语言的大学生,大学教师使用。

    数字电路-四位二进制乘法器课程设计

    【四位二进制乘法器】是数字电路中一种重要的逻辑设计,主要用于实现二进制数的乘法运算。在本课程设计中,学生需要设计一个能够处理4位二进制数乘法的电路,这涉及到对时序逻辑电路的理解和应用。时序逻辑电路是一...

    C++实现十进制转二进制运算(改进版)

    在二进制中,每一位的权重都是2的幂次,例如最右边的位(最低位)权重是2^0=1,向左依次是2^1、2^2等。 2. **十进制转二进制**: 通常,我们可以使用“除2取余”法来实现这个转换。将十进制数不断除以2,每次得到...

    二进制运算及转换PPT课件.pptx

    二进制运算及转换PPT课件 二进制运算及转换是计算机技术中非常重要的一部分。了解二进制运算及转换的原理和方法,对于计算机技术的应用和发展具有重要的意义。 在日常生活中,人们广泛使用的是十进制数,但是...

    4位同步二进制加法计数器74LS161实验电路multisim源文件

    4位同步二进制加法计数器74LS161实验电路multisim源文件,multisim10及以上版本可以正常打开仿真,是教材上的电路,可以直接仿真,方便大家学习。

    运算基础二进制的运算和计算机中的四则运算.ppt

    "运算基础二进制的运算和计算机中的四则运算" 在计算机科学中,了解不同的数制是非常重要的。了解二进制、八进制、十六进制、十进制等数制,可以帮助我们更好地理解计算机内部的信息表示和处理方式。 首先,让我们...

    整数位运算和二进制显示.zip

    标题中的“整数位运算和二进制显示”暗示了这个实验项目主要涉及Java编程语言中的位运算操作以及如何在图形用户界面(GUI)中显示二进制数字。位运算在计算机科学中是非常基础且重要的概念,它们允许我们对整数进行...

    八位二进制转三位十进制带数码显示的电路multisim源文件,multisim13以上版本可打开运行.zip

    该资源是一个基于Multisim软件设计的电路源文件,用于实现八位二进制数到三位十进制数的转换,并且带有数码显示功能。这个电路设计对于理解数字逻辑、电子工程以及计算机硬件原理有着重要的实践意义。下面将详细介绍...

Global site tag (gtag.js) - Google Analytics