二进制位运算--写出2*8的最有效率的运算方法

 

位运算,一种逻辑运算,直接操作二进制中的位。

面试时有时候会问的 :写出2*8的最有效率的运算方法 之类的问题,考的其实就是对底层二进制的熟悉程度。用位运算效率最高了。

 

按位与(&)

相对应的二进制位同为 1 结果才为 1,否则都是 0,形如:0&0=0,0&1=0,1&0=0,1&1=1 。 利用这个特性,我们判断奇偶数就可以不用再传统的 n%2的方式了,直接用 n&1,结果为 1 就是奇数,为 0 就是偶数。

按位或(|)

相对应的二进制位只要有一个为 1 ,结果即为 1,形如:0|0=0,0|1=1,1|0=1,1|1=1。

按位异或(^)

相对应的二进制位数字不同,结果为 1 ,否则都是 0 ,形如:0^0=0,0^1=1,1^0=1,1^1=0。异或有个特性就是,任何数与 0 异或,结果都是其本身。利用这个特性,可用于数的交换,以此可以解决一些面试刁难:

 

如何在不采用临时变量的情况下实现两个数的交换?当然,不用位运算也是可以实现的,只是不那么高级。常见写法奉上:

 

int a=2;int b=3;

//方式一

a=a+b; b=a-b; a=a-b;

//方式二

a=a^b; b=a^b; a=a^b;

取反(~)

二进制位按位取反,0 变 1 ,1 变 0 。

左移(<<)

形如 a<<b,将 a 的各二进制位整体向左移 b 位,高位溢出位移出,低位补 0。在数值没有溢出的情况下,左移n位相当于乘 2 的n次方。例如 2<<3,即由二进制的 00000010 变成了 0010000,相当于 2 乘 2 的 3 次方,结果为 16。因为位运算是 CPU 直接支持的,这也就是上面提到的 2*8 最有效率的运算方法了。

2*8==2*2^3==0010<<3==0010000==2^4==16

右移(>>)

形如 a>>b ,原理同左移,只不过由于符号位在最高位,所以,如果右移的是负数,会在高位补 1 ,如果为正数,高位补 0 。

无符号右移(>>>)

与右移唯一的不同在于,不论原来最左边是什么数,移动后都在高位补 0。注意,没有无符号左移, 因为左移始终是在右边补 0 ,而符号位在左边,不存在补符号位的问题。

 

二进制

  二进制,是计算机唯一能识别、存储的数,用0和1两个数码来表示,基数为2,“逢二进一”,”借一当二”。

  要搞清楚上面 Java 代码的运算逻辑,我们首先要做的是将对我们人脑直观的十进制数字转换成对计算机直观的二进制,这里就用到了一个概念叫比特位(bit),这是计算机最小的存储单元了,表示二进制的存储位。而我们说 一个字节占用 8 个长度位,就是指一个字节占用了八个比特位的长度,也就是八个二进制位。布衣博主画了一份草图,来将上文中的十进制数转换成二进制比特存储位,这里先以十进制的 256 为例:

将4字节的int类型数据转换成单字节的byte,最高位的三个字节的存储单元将被舍弃掉,这才是损失精度的要义所在!所以,根据上图高位舍弃的强转后,你自己也可以看出来,最后得到的 byte 十进制表示数字 0 。嗯,似乎也就那么回事,还是很好理解,但是,沿用上面的图,我们换成 128 试试?

看草图,似乎也很简单,128强转后,按照高位舍弃理论,无非是舍弃掉了高字节位无意义的 24 个 0 而已,最后的 byte 字节表示的还是原来那么大,还应该是 128 才对啊,为什么实际程序运行的结果却变成了 -128 ?咳咳!老师有没有告诉过你,Java的数据是带符号的?你知道二进制中如何表示一个数的正负的吗?所以,上诉理论中,我们还遗漏了一个很重要的知识点,那就是符号位的表示。对于有符号二进制来说,为了区分数的正负,约定以最高位作为符号位,0表示正数,1 表示负数,除去符号位剩下的就是这个数的绝对值部分:

我们带上符号位,回过头来重新分析上面对 128 的强转:当高位的三个字节被舍弃掉之后,连同舍弃的还有它的符号位 0 ,最终的结果就是强转成单字节后,原来表示数值部分的 1 变成了符号位,表示为负,除去符号位,能表示值的就只有后7位的 0000000 了。这样表示的十进制值为  -0,在带符号的二进制中,-0 被规定用来指代 -128,+0 才表示 0 。看来,只要带上符号位,本文最开始的输出结果是很好分析的。至此,我们引出了二进制中的符号位,并用此解答了本文一开始的疑惑。但是,有了符号位,这里又有疑问了,如果符号位占据了字节高位(第一位),当我们在进行算数运算的时候,符号位又该如何处理呢?

 

二进制领域中有原码,反码以及补码等等概念,下面是三种码基本的表示的方法:

  • 原码:符号位(字节序列的最高位)加上原数值绝对值的二进制表示;
  • 反码:正数的反码是其本身,负数的反码为保持符号位不变其余位置按位取反;
  • 补码:正数补码依旧是其本身,负数补码为反码加1;

  其实,引入反码,我么已经可以将减法统一变作加法【 1-1=1+(-1)】进行正确的计算了,已经解决了符号位的问题了,但会产生 -0 和 +0 的问题,也就是 0 被带上了符号。虽然在人脑看来是正负 0 一样的,但是计算机可不那么认为,而且按照定义 0 会有两种原码表示,即 000 0000 和 1000 0000,这显然是有问题的。于是在反码的基础之上加 1 变补码,彻底解决了正负 0 的问题,以前表示 -0 的1000 0000 现在可以用来表示 -128,因为 -128 = -1-127=(-1)+(-127)=(1111 1111)补+(1000 0001)补=1000 0000。——这也是带符号位二进制能够多表示一个数的原因。下面是博主探究二进制运算的过程中画的原码和补码计算的结果差异图:

 

 

上图至少说明了两点:

第一,带符号二进制直接用原码进行加减运算特别不靠谱,而通过补码进行加法(减也看作加)运算很靠谱;

第二,如果运算结果是正数,由于正数的原码和补码相同,所以结果和十进制数是正确匹配的,如果结果是负数,需要将补码转成原码方能匹配正确的十进制结果;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值