cpp/C语言位运算及应用实例

位运算简介与应用例子
简介

位运算:数据在计算机中以01二进制的形式储存,位运算就是直接对这二进制数据进行运算
几种位运算及运算示例如下:

首先是与、或、异或、取反:
对于bool:

  1. &0&0=0 0&1=0 1&0=0 1&1=1
  2. |0|0=0 1|0=1 1|0=1 1|1=1
  3. ^ 异或 0^0=0 1^0=1 0^1=1 1^1=0
  4. ~ 取反:~0=1; ~1=0

对于int的运算:(按int 占四个字节32比特来看
对于int的与/或/异或/取反(此处均为正整数且数字较小,前边的28位二进制位均为0不影响运算略去)就是对两个int型数据的二进制的对应的每一位进行上面的运算
对于(signed )int:
3 & 5 = 0b0011 & 0b0101 = 0b0001= 1
3 | 5 = 0b0011 | 0b0101 = 0b0111 = 7
3 ^ 5 = 0b0011 ^ 0b0101 = 0b0110 = 6
对于unsigned int:
3 & -5 = 0x00000003 & 0xfffffffb = 0x00000003 = 3
3 | -5 = 0xfffffffb = -5
3 ^ -5 = 0xfffffff8 = -8

对于(signed )int: ~0=-1; ~1=-2
~0=~0x00000000=0xffffffff=-1
~1=~0x00000001=0xfffffffe=-2
~3=~0x00000003=0xfffffffc=-4
~(-5)=~0xfffffffb=0x00000004=4
(正的int型变量n取反相当于-(n+1)
对于unsigned int :~0=4294967295;~1=4294967294
~0=~0x00000000=0xffffffff=4294967295
~1=~0x00000001=0xfffffffe=4294967294
(正的unsigned int型变量n取反相当于(4294967295-n)
然后是左移、右移运算符:
5. << 左移(符号前的数的二进制整体向左移动符号后的数位,左侧超出范围的忽略,右侧补零) 1<<0=1 1<<1=2 1<<2=4 (0b0001<<1=0b0010=2)
6. >> 右移(左侧补零) 4>>0=4 4>>1=2 4>>2=1 4>>3=0 4>>4=0(同左移)
经测试:
对于(signed )int:当0<=n<=30时,1<<n就是2的n次幂。而1<<31=0x00000001<<31=0x80000000=-2147483648,说明左移运算是会把‘1’移动到符号位上的。当n>=32时,1<<n = 0。

上述运算对于char, short int, long, long long类似,只是二进制位数不同
位运算不适用于float,double等

一些应用示例

对于一些简单的函数,可以加上inline;或者在程序中直接用,不再封装成函数

  1. 判断数字奇(odd)偶(even)
    如果数字二进制的最后一位是1,数字就是奇数,是0就是偶数。负数也可以,-1&1=0xffffffff & 0x000001 = 0x000001 = 1;-2&1=0;-3&1=1;…
inline bool isOdd(int n){return n&1;} // 是否是奇数 
inline bool isEven(int n){return !(n&1);} // 是否是偶数
  1. 大小写字母转换
    比如
    ‘A’=65=0b1000001
    ‘a’=97=0b1100001
    因为同一个字母大小写的ASCII码值正好差了97-65=32=2^5=0x20,二进制就只有一位不同。
    如果确定输入为大写或小写字母,可以用下面的:
inline char _lower(char c){return c+0x20;}
inline char _upper(char c){return c-0x20;}

或者

inline char _lower(char c){return c|0x20;}
inline char _upper(char c){return c&0xdf;}

不确定的话:

// 对于不正确的输入,原样返回不做处理
char tolower(char c){return (c>='A' && c<='Z')?(_lower(c)):c;}
char toupper(char c){return (c>='a' && c<='z')?(_upper(c)):c;}
  1. “字节翻转”
    比如int n=0x12345678;想要int m=0x78563412;或者想要int l=0x87654321;
    甚至char c=0b01011011;想要char h=0b11011010;
//注 下面的12345678代表char二进制的八位,不代表二进制的值
// 注: 不考虑最高位表示正负,连带着最高位一起翻转
char revChar(char c){ //c=0b12345678;
    c=(c<<4)|(c>>4); //c=0b56781234;
    c=((c&0x33)<<2)|((c&0xcc)>>2);//c=0b78563412;
    c=((c&0x55)<<1)|((c&0xaa)>>1);//c=0b87654321;
    return c;
}
// 其他如int,long等同理,按int占4字节算
int revInt(int n){ // n=0x12345678;
    n=(n<<16)|(c>>16); //0x56781234;
    n=((n&0x00ff00ff)<<8)|((n&0xff00ff00)>>8); //0x78563412;
    // 注:到这里是按字节翻转,把四个字节反着来了
    // 注:根据需要还可以继续翻转,如下
    // 注: 0x3=0b0011,0x5=0b0101,0xa=0b1010,0xc=0b1100
    // n=((n&0x0f0f0f0f)<<4)|((n&0xf0f0f0f0)>>4); //0x87654321;
    // n=((n&0x33333333)<<2)|((n&0xcccccccc)>>2);
    // n=((n&0x55555555)<<1)|((n&0xaaaaaaaa)>>1);
    // 注:到这里是每个比特都翻转了
    return n;
}

可根据自己需要,自行更改变量类型和翻转几次翻转到什么程度。

  1. 对int,设置某一位,取某一位的值
// 对于int占四字节,0b00000000 00000000 00000000 00000000
// 从右到左的32个比特依次设为int的第0到第31位,当然也可以设为1-32
// 下面的ind应该大于等于0,小于32;(这样写总是可以运行,
void setBite(int n, unsigned int ind, bool f){
	// f为真:把n的第ind位设为1
	// f为假:把n的第ind位设为0
	if(f) n |= 1<<ind;
	else n &= ~(1<<ind);
}
inline bool getBite(int n, unsigned int ind){
	// 获取n的第ind位
	return (n>>ind)&1;  // 括号可以省略
}
inline void revBite(int n, unsigned int ind){
	// 对n的第ind位取反
	n ^= 1<<ind;
}

可以把int n改成long n等等;也可以写到#define那里
5. 统计二进制中‘1’的个数
(记得有个前面带”_“的内置函数可以的,但找不到了)

// 取最后面一个'1'
inline int getLastOne(int n){return n&(-n);}
// 比如n=12=0x0000000c -n=-12=0xfffffff4 12&(-12)=0x00000004=4
// 其中0xc & 0x4 = 0b1100 & 0b0100 = 0b0100 = 4

// 去掉最后面一个'1'
inline return f1(int n){return n&(n-1);}
inline return f2(int n){return n-n&(-n);}

// 统计n中'1'的个数,一个示例。(方法很多,
int numOfOne(int n){
	int ret = 0;
	while(n){n=n&(n-1);++ret;}
	return ret;
}

下面的代码来自:维基百科:汉明权重
来自:Colin丶c语言:统计整数二进制表示中1的个数(汉明重量)
把维基百科里64bit的改成了4字节int的32bit的

int numOfOne(int n){
	n = (n&0x55555555) + ((n>>1)&0x55555555);
	n = (n&0x33333333) + ((n>>2)&0x33333333);
	n = (n&0x0f0f0f0f) + ((n>>4)&0x0f0f0f0f);
	n = (n&0x00ff00ff) + ((n>>8)&0x00ff00ff);
	n = (n&0x0000ffff) + ((n>>16)&0x0000ffff);
	return n;
}

类似分治,把32bit每个比特为一组,两两相加
0b0 0 1 1 0 1 1 0 - 0 0 0 0 0 0 0 0 00000000 0000000 0
--1 2 3 4 5 6 7 8 - 910111213141516-................ 32从左到右依次编号1-32
先把,1和2,3和4,5和6,…31和32处的,0或者1相加,结果存入相应位置
比如,0+0=00存入编号1和2的位置(表示12位置没有1),1+1=10(二进制)存入3和4的位置,0+1=01存入5和6的位置。变为这样:
0b0 0 1 0 0 1 0 1 - 0 0 0 0 0 0 0 0 00000000 0000000 0
--1 2 3 4 5 6 7 8 - 910111213141516-................ 32
然后两个比特一组,两两相加
比如00+10=0010存入编号1234的位置(表示1234位置有两个1),01+01=0010存入编号5678的位置…
然后四个比特一组,两两相加
比如0010+0010=00000100存入12345678的位置(表示12345678位置有4个1)
然后八个比特一组,两两相加;最后16个比特一组两两相加,存入n
n的大小就是这32比特中1的个数。
7.
比如取int的低4位 n&0xf ;低8位 n&0xff;低9-16位 (n&0xff00)>>8
8. 最后,float和double类型也不是不能进行位运算,但是数字的表示方法不一样
对于4字节的float是:1比特符号位,8比特指数位,23比特的小数位

float f=1.0;
// 0b-0-01111111-00000000000000000000000
// 符号位0表示正数,指数位为127-127=0,小数位为1.0,
// 指数要减2^7-1=127,double的指数要减2^10-1=1024
// 因为都是 1.???,省去了前面的1
// +1*1.0*2^0=1.0
int * n = (int *)&f;
// 如果直接输出printf("%x",*n);可以得到 0x3f800000=0b00111111 10000000 ...
(*n) |= (1<<31);  // 这样f就变成了-1.0,因为将符号位设为了1
(*n) |= (0xf<<23);  // -1*1.0*2^(255-127) --> inf 指数位设为了11111111
(*n) &= ~(0b01111110<<23); // -1*1.0*2^2=-4.0 指数位设为了10000001

对于小数位,从紧挨着指数位的那一位算起,那一位为1表示要加2^(-1)=0.5变为1.5,往远离指数位的方向依次计算,
比如0b 0 01111111 11010000000000000000000
表示+1*(1.0+0.5+0.25+0+0.0625)*2^0=1.8125
以上,就知道了float32位的每一位都代表什么,可以进行操作实现自己想要的效果了。

有一个例子:
以下来自https://h14s.p5r.org/2012/09/0x5f3759df.html
虽然有小误差,但运算速度比较快。

// 计算一个float的二分之一次方的倒数,即1/sqrt(x)
float FastInvSqrt(float x) {
  float xhalf = 0.5f * x;
  int i = *(int*)&x;
  i = 0x5f3759df - (i >> 1);
  x = *(float*)&i;
  x = x*(1.5f-(xhalf*x*x));
  return x;
}

对于8字节的double:1比特符号位,11位指数位,52位小数位。类似float

如有错误,欢迎指正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今夕何夕2112

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值