位运算总结

位运算总结

在大学学习点亮LED灯时第一次接触到位运算,通过将0x0001连续左移,赋值给对应IO口,实现LED灯的循环点亮,相比于 置位与复位,位运算简化了编程,也更容易理解程序内部数据如何变化。
本文章是对位运算的部分总结,希望和朋友交流想法。
了解位运算之前,需要对有符号数,无符号数,补码进行了解。

无符号数,有符号数

深入理解计算机系统一书中采用一个函数B2U (binary to unsign) 将一个二进制向量转化为无符号数, 比如0x0111=7; 0x1001=9。在一个32位的编译器中,一个unsigned int型变量有4个比特,32位二进制数构成,其所所能表示范围是2^31-1=42 9496 7295 将近43亿,由于unsigned int所表示的数据有上限,所以经常看到某些游戏过程中 怪的血量或者攻击叠到一定程度会突然变为0。有符号数将
数据最高为看作符号位 0表示整数,1表示负数。 0111表示+7,1111表示-1.
1 1 1 1
-8 +4 +2 +1 = -1
对于有符号数0xFFFFFFF就表示-1. 由于 0 用二进制表示为0x00000,所以
~0(非零) = -1; 你计算6-1也可以写成6+~0.当然谁也不愿意这么去写,但当你
键盘“-”不小心坏掉,你就可以for (int i = 5; i>0; i += ~0) 继续了(狗头)
4位2进制数表示范围
获取 int 表示的最大值方法:

cout << (1 << 31) ;        //int 型最小值    0x100...000
cout << (1 << 31) - 1 ;    // int型最大值    0x011111...11   最小值减1得到最大值
cout << ~(1 << 31) ;       // int型最大值    0x011111...11
cout << (((unsigned int)-1) >> 1)  //

补码

现代计算机中保存数据均用补码表示,及解决负数加减问题…我们常听到补码
网上搜会有两种 1’s complement&2’s complement 及1补数和2补数。两者区别是
1补数是按位取反,2补数在1补数基础上+1.2补数方便了计算机电路设计,一种电路
就可以计算正负数加减法。举个例子:
5 - 3=2 -6-3=-9
0101 +5 1010 -6
1101 -3 1101 -3
=0010 +2 = 10111 -9
补码的计算在数值输入时,编译器就已经计算好。

位运算

1位运算操作符有 & | ~ ^(和&&、||、!相区分)以及>> <<。
1、^异或操作及相同取0,不同取1。可以看作不进位加法,eg 01^11=10;
异或性质如下,熟记编程过程会渐变很多。
i、 任何数与0异或等于该数 1001^0=10001
ii、 异或满足交互率 abc=cab
iii、相同的数异或结果为0 a^a=0;
leetcode上有很多位运算题目可以应用此性质方便解出。基于以上性质,我们可以重新实现swap()函数;如果我们想交换AB的值,通常会新建一个tmp中间变量。可以通过
a=a+b;b=a-b;a=a-b 实现。使用异或操纵与第二种类似,即
a=a^b;
b=a^b;
a=a^b;
上面说异或可以看作不进位加法,与运算可以求取进位,所以两者结合可计算 a+b;

int add(int a, int b) {
	if (0 == b) {  
		return a;
	}
	return add(a ^ b, (a & b) << 1);
}

有补码性质,-a=~a+1 同样我们可以实现减法; a-b=a+1+~b;
判断a,b是否异号

bool issame(int a,int b){return (a^b)<0;}  //小于0即首位不同

2、统计二进制中1的个数
此问题时二进制的经典问题,网上有很多解答,在此做一下总结。最简单的,将二进制每一位与0x1做与运算,对于一个int型数据,需要循环32次。第二种常用方法是通过 num&(num-1)计算 num中1的个数,该算式每计算一次,num中最右边一个1变为0 .所以该方法运行num中1的个数次**(一个数
字减一,最右边一个1之后所有数字将取反)**。
对于方法2的延伸可以判断一个数是否是2的整数次幂,2的整数次幂满足该数的最左边一位为1,所以 计算num&(num-1)结果为0,则num位2的整数次幂。

int cnt_one(int num)
{
	int cnt = 0;
	while (num != 0)
	{
		num = num&(num - 1);
		cnt++;
	}
	return cnt;
}

还有一种方法,代码如下:

 int bitCount(int x) {
  		int mask1=0x55555555;
  		int mask2=0x33333333;
  		int mask3=0x0f0f0f0f0f;
  		int mask4=0x00ff00ff;
  		int mask5=0x0000ffff;
 		int ans=(x&mask1)+((x>>1)&mask1);
  		ans=(ans&mask2)+((ans>>2)&mask2);
        ans=(ans&mask3)+((ans>>4)&mask3);
        ans=(ans&mask4)+((ans>>8)&mask4);
        ans=(ans&mask5)+((ans>>16)&mask5);
        return ans;
        }

怎么理解呢,mask1计算每两位1的个数,mask2则计算每4位1的个数,最后
计算整个32为数1的个数。
3.求一个序列的子集。
二进制每一位只有两个值0,1。好处就是可以判断每一位的有无,比如求abc的子集。即可用三位二进制数表述 000->111。
4.计算m的n次幂
常用方法是用一个n次循环,时间复杂度位O(n),下面考虑用位运算
eg 计算3的11次幂 311=3^0x1011=3 ^0x1000+3 ^0x0010+3 ^0x0001
code:

int pow(int m,int n)
{
	int sum=1;
	while(n!=0)
	{
       if(n & 1 == 1)
       {
           sum *= m;
       }
       m*= m;
       n = n >> 1;
   }
   return sum;
}

5.算log2
32位数字计算对数,最大值就是32。可以用5位二进制数表示:

	int ilog2(int x) {
  	int ans=0;
  	ans=(!!(x>>(16)))<<4;       // 16~31位是否有值,有的话ans=10000
  	ans=ans+((!!(x>>(8+ans)))<<3); //    01000
  	ans=ans+((!!(x>>(4+ans)))<<2); //    00100
  	ans=ans+((!!(x>>(2+ans)))<<1); //    00010
  	ans=ans+((!!(x>>(1+ans)))<<0); //    00001
  	return ans;
}

6.取绝对值

int abs(int n)
{  
    return (n ^ (n >> 31)) - (n >> 31);  
}

解释一下,n>>31 位判断数字n的正负号(针对32位int数据),如果n为正数,
原式等于 n^0 - 0 = n;当n为负数时,原式等于(n ^-1)-1 ,这不就是取相反数过程嘛。
7.大小写字符转换
小写字母 a~z 97 ~ 122 小写字符减0x20即可。
观察其二进制 0b0110_0001~0b0111_1010 只要将第六位1置零即可
大写字母A~Z 65 ~ 90 二进制表示为 0b0100_0001 ~ 0b0101_1010
将第六位0置一即可。

统一转成大写:ch & 0b11011111 简写:ch & 0xDF
统一转成小写:ch | 0b00100000 简写:ch | 0x20

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1="your str";
	string s2;
	for (auto str:s1)
	{
		str^= 0x20;
		s2+=str;
	}
	cout << s2 << endl;
}
  1. 获取最低位的1
x & -x == x & (~x + 1)

当x为奇数,他取反后肯定是个偶数,再加一只有最低为变化,其余都没变,最后结果为1.
结论:x是奇数, 那x & -x 的结果一定是1
当x为偶数,有两个重要特性

这个结果只有一位值是1, 其他位均是0 
这个值的末位0的个数与原值保持一致

最后结论:当一个数与其取负后的值相与, 如果这个数是偶数, 则结果是能整除这个偶数的最大的2的幂(即: m = n & -n , 则 n % m = 0, 且 m = 2 ^ k), 如果这个数是奇数, 则结果必为1
参考博客
用途:获取某个二进制位的低位
leetcode题目

9.n&(n-1)的用途
判断一个是否是2 的幂 参考3

10 dd

参考

参考链接1
参考链接2
参考链接3

未完待续。。。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值