Bit Operation

前言

  计算机中的所有数据都是按照bit为单位进行存储的,由于二进制存储的特殊性,编程实现中出现了很多位操作技巧,这里做一下总结。总结的内容借鉴了网友博客、书籍内容,然后还有个人理解,侵删。

1-存储数字

先来看一道牛客上的题目:

int main(){
   int i=-2147483648;//这里有问题,后文说
   printf("%d,%d,%d,%d",~i,-i,1-i,-1-i);
   return;
}

结果是:2147483647,-2147483648,-2147483647,2147483647

  首先,计算机中存储数字都是按照其补码来存储的,好处是将加减法统一为补码的加法,也就是将+、-符号转换为数字本身的一部分。整数的原码、反码、补码相等,就是原码;负数的补码是通过原码求反码,反码+1得到的结果。同时规定:
  0的补码0x00000000-2147483648的补码:0x80000000

~i
  i的补码为1000 0000 0000 0000 0000 0000 0000 0000,取反0111 1111 1111 1111 1111 1111 1111 1111,此为补码,符号位为0,表示正数,正数原码补码一致,因而该数即表示2^31-1,即2147483647 。注意,这里的取反(~)是数值位和符号位同时取反,在负数求反码取反过程中只是数值位取反。
  
-i
  要对一个数值执行单目运算符-,表示的是对该数取反然后再+1,也即是我们常说的求补运算,注意这里取反+1与原码求补码的区别!求补运算与求补码是不一样的!例子(4位有符号整数):x=-4 1100(补码) -x=~x+1 也即是 0011+0001=0100(4),而1100再求补码应是先数值位取反,即1011,然后+1,变成1100!

1-i
  我们已经求出-i的补码为1000 0000 0000 0000 0000 0000 0000 0000 加上1的补码即为1000 0000 0000 0000 0000 0000 0000 0001。该补码表示的原码为1 111 1111 1111 1111 1111 1111 1111 1111,即为-2147483647。如果这里想通过补码直接验证对应数字的具体值,那么可以把补码转换成对应的十六进制形式,直接打印即可。cout << 0x80000001;打印结果就是-2147483647
  
-1-i
  -1的补码为1 111 1111 1111 1111 1111 1111 1111 1111,加上-i补码 1000 0000 0000 0000 0000 0000 0000 0000,得0111 1111 1111 1111 1111 1111 1111 1111,即 2147483647

2-0x8000000

  上文中提到代码中int i = -2147483648;这段代码有一定问题。问题和编译器有关。在cb中这是没问题的,i就是INT_MIN;但是在VS2013中,编译器先判断2147483648并不在int类型范围内,自动转换为unsigned int类型,然后给unsigned int类型赋予负号,这里就错了。如果想在VS中获得INT_MIN,应该写如下代码:
int i = 0x80000000;

还有一个典型问题,看如下代码。

if (1 < 0x80000000)//数字1,不是字母l
    cout << "ok1";
int i = 0x80000000;
if (1 < i)
    cout << "ok2";

  运行结果只输出ok1。为什么呢?因为如果你在代码中直接写0x80000000,那么编译器(cb和vs都是这样)是直接判断2147483648这个数字不在int范围内,提升为unsigned int类型,它的值就是2147483648,而不是预想的-2147483648。但是如果你对一个int变量赋予值0x80000000,折相当于告诉了编译器这是一个signed int类型,需要按照有符号数的方式去存储它,所以这时i的值就是-2147483648

如果你写了如下代码

int i = 0x80000000;
cout << (unsigned int)i;

那么,输出将是2147483648,编译器按照无符号数字来处理。

3-数字元整问题

  在SGI-STL的二级配置器源码中,有这么一个功能函数,是把用户申请的以bytes为单位的空间大小(size_t n)向上转换成8的倍数。如7->8,9->16;
  
这里的代码是:

//_ALIGN = 8;
static size_t ROUND_UP(size_t n) {
    return ((n) + _ALIGN-1) & ~(_ALIGN-1);
}

  根据二进制数字的性质,那么一个数字的二进制形式的最后三位是0,那么这个数字肯定就是8的整数倍;但是需要额外考虑的情况是,n二进制形式的最后三位原本不是0,比如9(1001),所以使用(n)+_ALIGN-1操作,进位后变为0,然后在使用& ~(_ALIGN-1)操作使得二进制数字最后三位是0。这里的位操作使用非常灵活,而且效率极高。

4-基本的位操作

Set a bit

number |= 1 << x;

Clear a bit

number &= ~(1 << x);

Toggle a bit

number ^= 1 << x;

Check a bit(is 1 or 0)

bit = (number >> x) & 1;

5-循环移位

int类型的变量,因为超过32位,会出现什么情况呢?
会将移动的位数对32取模运算,根据余数进行移位,这就是循环移位。

但考虑一下下面的代码(在codeblocksGNU GCC Compiler)

    int a = 0x00000001;
    int b = 0x00000001;//a和b都是1
    b<<=32;//直接移位32位,发生循环移位,32%32==0,相当于不移位
    cout << b << endl;
    cout << a <<endl;
    a<<=31;//这时候等于INT_MIN(-2147483648)
    cout << a << endl;
    a<<=1;//分步移位,这时候在向左移一位就是0
    cout << a;//0

如果还想了解更多的位操作内容,可以浏览StackOverflow
BIt Operation总结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值