educoder 二进制数据的位运算_二进制与位运算实用操作汇总(基础篇)

位运算是最高效而且占用内存最少的算法操作,但也是最难看懂的操作。然而,关于位运算的用法,笔者查了许多资料,似乎都没有找到详细而系统的讲解资料。笔者对位运算的操作相当感兴趣,因此斗胆尝试对位运算来一个的总结。

本文先从基本概念出发,然后从基本概念推导出基础应用,然后再到算法题实战。层层推进,逐步迭代。

本人水平有限,如有勘误,敬请指正。

说明:本文会以Python的交互环境来做代码演示。关于本文的约定:非代码块中的二进制数以下标x来表示进制数,如十进制数15,用二进制表示为

,而用十六进制则表示为

所有代码块中的二进制数字都以0b开头,比如十进制数15,用二进制表示为0b1111;

有时候需要更直观地表示,会使用竖式表示,如两个二进制数的and操作表示为:

所有代码块中的十六进制数字都以0x开头,比如十进制数255,用十六进制表示为0xff;

bin()函数,是Python中把十进制转换为二进制的转换函数;

所有的位运算操作的命名均用英文命名,以增加辨识度。

概念篇

and 操作,操作符“&”

定义:称为按位与运算符。它对整型参数的每一个二进制位进行布尔与操作,即两个对应的二进制位同时为1时,才等于1。

or 操作,操作符“|”

定义:称为按位或运算符。它对整型参数的每一个二进制位进行布尔或操作,即两个对应的二进制位,任意一个为1时,就等于1。

xor操作,操作符“^”

定义:称为按位异或运算符。它对整型参数的每一个二进制位进行布尔异或操作,即两个对应的二进制位,有且仅有一个为1时,才等于1。

not操作,操作符“~”

定义:称为按位非运算符。它是一个单运算符,对运算数的所有二进制位进行取反操作。

shl操作,操作符“<

定义:称为按位左移运算符。它把第一个运算数的所有二进制位向左移动第二个运算数指定的位数,而新的二进制位补0。将一个数向左移动N个二进制位相当于将该数乘以2的N次方,比如:

shr操作,操作符“>>”

定义:称为按位右移运算符。它把第一个运算数的所有二进制位向右移动第二个运算数指定的位数。为了保持运算结果的符号不变,左边二进制位补 0 或 1 取决于原参数的符号位。如果第一个运算数是正的,运算结果最高位补 0;如果第一个运算数是负的,运算结果最高位补 1。将一个数向右移动N个二进制位相当于将该数除以2的N次方,比如:

(总是向负无穷方向取整)。

原理篇

进制转换

进制之间的转换其实是个数学问题,在实际应用中,我们基本上无需操心。因此这里想说的不是计算问题,而是逻辑问题。二进制与十六进制有着天然的联系——每四个二进制位可以代表一个十六进制位:

由上图可见,如果说二进制转十进制还要稍稍心算一下的话,那二进制转十六制可以马上得出。只要记住了一个十六进制位与四个二进制位的对应关系就好了。同理,八进制位与二进制的关系是,每三个二进制位对应一个八进制位,但实践中,八进制并不常见,因此点到即止。

那么,为什么二进制与十六进制在实践中更常见呢?这是与内存的储存单位有关。

字节与二进制数的关系

对于计算机而言,最小的储存单位是一个字节,英文为byte。byte既是一个单位,也是一种数据类型(关于数据类型的解释,可查阅C/C++、JAVA等静态类语言的相关资料,本文不作介绍)。对于一个byte的数据,用了八个二进制位去储存数据,因此能正好用两个十六进制位来省略表示(比十进制还少写一位)。这就是为什么在实际操作中,二进制与十六进制更常见的原因。

二进制运算符的操作范围

笔者查阅了许多二进制运算的相关资料,似乎都忽略了对这一点的介绍。二进制运算符的作用范围与参与运算的变量的数据类型有关,比如以JAVA为例:对于byte类型变量,由于byte以8-bit(8个二进制位)表示,因此相应的位运算符的作用范围就是8-bit;

对于int类型变量,由于int以32-bit表示,因此位运算符的作用范围就是32-bit;

假如两个大小不一的数据类型进行操作,那位运算的作用范围会以较大的数据类型作为基准范围。

二进制数的符号

有了数据类型的范围限定,因此才有了高位、低位、符号位的概念。高位,指在数据类型限定范围内靠左的二进制位;

低位,指在数据类型限定范围内靠右的二进制位;

符号位,指在数据类型限定范围内最左边的一个二进制位,符号位为0表示正数,1表示负数。(除了C语言存在无符号的值类型外,绝大部分语言的值类型都默认为有符号的数值类型)

因此,假如一个byte范围内的整数,提升为一个int范围内的整数,由于二进制位数的范围大了,必然需要进行补位,因此:当原byte整数为正数时,提升为int类型时,补位全部以0补位;

当原byte整数为负数时,提升为int类型时,补位全部以1补位;

为什么要这样做?因为这样才能保证数值在范围提升后,原值的十进制数不变。以下来看看JAVA的验证代码:

// byte类型的值范围是[-128,127]byte a = (byte) -55; //由于值在byte范围内,因此强制转换缩小变量内存范围不会改变原值byte b = (byte) 100;

System.out.println(Integer.toBinaryString(a)); //输出(二进制数):11111111111111111111111111001001System.out.println(Integer.valueOf(a)); //输出:-55(重新提升范围,值不变)System.out.println(Integer.toBinaryString(b)); //输出(二进制数):1100100(高位的0会被省略显示)System.out.println(Integer.valueOf(b)); //输出:100

二进制下的负数表示法

对于一个十进制的负数,我们经常把它看作是一个数加一个负号;然而对于二进制负数来讲,却不是一堆二进制位数加一个符号位。

二进制的正数与负数之间的关系更像是“进位”的关系。以下我们以一个byte值来举例:

如上所述,byte的数值范围是[-128,127]。

为了让表示更清楚,笔者特意把符号位隔开。留意从0到-1,由于非符号位全部为0,已经没有东西可减,但假如我们假设从更高的位借来了一个1,这样就能让

了;有了-1,那

就可以继续成立了……直到把除符号位之外的位全部减完,这时十进制数就相当于-128。因此,八位二进制数可以表达的数为

个,即[-128,127]。

二进制数的性质

由以上的二进制数变化规律,我们可以发现二进制数有以下性质:~x = -x - 1:从以上0与-1的按位关系可以看到,两者的二进制位正好取反;此规律能推广到1与-2,2与-3……等等。因此,该性质是not操作中最常使用的性质。

(~x)&x = 0:任意数与它的取反数的and操作结果为0。

(~x)|x = -1:任意数与它的取反数的or操作结果为-1。

(~x)^x = -1:任意数与它的取反数的xor操作结果为-1。

x|0 = x:任意数x与0的or操作结果为x自己。

x^0 = x:任意数x与0的xor操作结果为x自己。

x^y^y = x:任意数x与任意数y进行两次xor操作结果为x自己。

与四则运算一样,位运算也有它自己的定律。因此,我们有必要先熟悉并证明一下这些定律。

定律篇

and操作

交换律,a&b = b&a

证明:略(因为显然易见)

结合律,(a&b)&c = a&(b&c)

证明:只要证明一个二进制位,便能推广到N个二进制位。

(1&0)&0 = 1&(0&0) = 0;

1&1&0 = 1&(1&0) = 0。

or操作

交换律,a|b = b|a

证明:略

结合律,(a|b)|c = a|(b|c)

证明:只要证明一个二进制位,便能推广到N个二进制位。

(1|0)|0 = 1|(0|0) = 1;

1|1|0 = 1|(1|0) = 1。

xor操作

交换律,a^b = b^a

证明:略

结合律,(a^b)^c = a^(b^c)

证明:

not操作

结合律,a = ~(~a)

证明:略

shl操作

shr操作

继续深入传送门:黄伟亮:二进制与位运算实用操作汇总(应用篇)​zhuanlan.zhihu.com

参考资料:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值