【Java】移位运算

以前一直没有研究二进制的移位运算的应用场景是什么,怎么运算?怎么实现数据的四则运算的? 直到最近,在看Think in
Java的书籍,才真正理解这个东西。下面记录一下学习笔记。

1,二进制

1.1 二进制的表示

我们知道,计算机中所有数据都是以二进制形式存储。例如1(int)在二进制中的表现形式就是
00000000 00000000 00000000 00000001。
而0的二进制就是所有位上均为0。
具体的根据不同的编程语言,可能对于基础数据类型有不一样的字节数的定义。
对于Java而言,Java的八大数据类型的字节数定义如下:

byte:8bits
int:32bits
char:16bits
short:16bits
long:64bits
boolean:-
float:32bits
double:64bits

以下是Think in Java中截取的基础数据类型的定义
这里写图片描述

1.2 二进制数据与十进制数据的关系

在二进制中,5表示为101,也就是5=1*pow(2,2)+1*pow(2,0)

1.3 正负数

在二进制中,需要有一个bit来表示是数据的正负,这bit就是数据的最高位。
1表示负数,0表示正数。

1.4 最大值和最小值

以int数据类型为例(下文中对于正数,如果位数没有足够的位数,笔者为了简单,会忽略高位的0,而从第一个1开始)
例如,int类型的5在计算机中的表达就是101。既然数据类型有字节数的限制,那么必然就会有该数据类型能够表达的范围。int类型的数据有32bits,因此最多能够表达的数字的个数就是2^32。

如果不考虑正负,那么32bits的数据能够表达的数据范围就是
00000…….000000(32个0)~11111……..1111111(32个1),也就是0到2^32-1。
但是1.2节讲到,数据有正负,因此在数据的表达范围就发生了变化。其真正的表达范围应该是
10000……000000(31个0)~011111……1111111(31个1)。也就是从 -2^31到2^31-1

看到这里,读者可能就有疑问了,为什么不是从00000……000000到1111111……1111111呢?

1.2中已经说过,最高位的1代表负数,0代表正数。因为000000……000000000代表0,最高位的0已经被占据了一位,那么剩下的31个bit能够表达的的数字也就是从0000……00000(31个0)到111111……11111(31个1),所以能够表达的就是从0到2^31-1。同理,对于负数的范围就是1000000……00000000到1111111……111111111,因此能够表达的就是从 -2^31到-1。

那么计算机中为什么是10000……0000000代表最小的值呢?

这其实是二进制的运算规则决定的。

例如,对于二进制的-1,其二进制的表达就是111111……111111,那么加1,就应该等于0(也就是0000000……000000)

这里写图片描述

所以,上面的-1的表达就是1111111111……111111111,按照这样的推算就可以知道最小数就是10000000……0000000000。
注:下面的部分会讲解相反数的计算,以及二进制数据的计算,这对于上面最大数最小数的表达的理解有帮助。

1.5 四则运算在二进制中的表达

那么,二进制是怎么进行计算的呢?

其实二进制和十进制的数据的计算规则也是一样的。
在十进制中,两个数相加的规则就是“逢十进一”,对于二进制同理,不过变成了“逢二进一”。相减就是如果被减数的位小于减数的位时,就向高位借1。

加法

规则:逢二进一
这里上面的1.3的例子已经很好的讲解了。那么这里还有一个问题,如果是按照逢二进一的规则,就会出现一种情况,就是最高位溢出的现象。也就是说最终计算出来的二进制数据超出了字节数的限制。那么应该将最高位的那个bit应该舍弃。

上面的1.3的例子就是一个很好的例证。

减法

规则:如果被减数的对应位置的数小于减数的上的数,那么就向其高位借2。简称”借一有二

例如1-2=-1
即为:

    00000……00001
-   00000……00010
    ——————————————
    11111……11111

乘法

由于二进制的乘法,每一位只有0和1,因此二进制的乘法更简单。

规则:
由低位到高位,用乘数的每一位去乘被乘数,若乘数的某一位为1,则该次部分积为被乘数;若乘数的某一位为0,则该次部分积为0。某次部分积的最低位必须和本位乘数对齐,所有部分积相加的结果则为相乘得到的乘积。

这里写图片描述

除法:
二进制数除法与十进制数除法很类似。

规则:先从被除数的最高位开始,将被除数(或中间余数)与除数相比较,若被除数(或中间余数)大于除数,则用被除数(或中间余数)减去除数,商为1,并得相减之后的中间余数,否则商为0。再将被除数的下一位移下补充到中间余数的末位,重复以上过程,就可得到所要求的各位商数和最终的余数。

这里写图片描述

1.6 相反数

二进制中,对于相反数的计算,就是”取反加1
例如-1的相反数就是1。
在二进制中就是1111111……111111取反加1,得到00000……0000001。

2,位操作

二进制的位操作主要有移位、位与、或、异或、非。
其中,对于char,byte,short都是以转换为int进行移位操作的。而对于double和float都是没有位操作的。

2.1移位

移位操作符有两种,左移位<<和右移位>>,其中类似于+=这种操作符一样,也有<<=和>>=。
左移:
例如,5<<2表示5向左移动两位
5的二进制表示就是101,那么左移两位之后,就是10100,也就是乘以4即等于20。
对于位数左移之后,低位的补0。
那么比如10000……000000(即最小数),那么它左移2位之后,它的二进制表达就是00000000……000000(即为0)

public class BitCal {
    public static void main(String[] args) {
        int max_i=Integer.MIN_VALUE;
        System.out.println(Integer.toBinaryString(max_i));
        System.out.println(Integer.toBinaryString(max_i<<2));
    }
}

输出的结果为:

10000000000000000000000000000000
0

右移:
和左移不一样的地方就是:如果被位移的数是负数,那么右移之后,高位全都补1;如果是正数,那么右移之后,高位全都补0。也就是正数依然是正数,负数依然是负数。

无符号右移
这种移位操作与右移不同的地方就是:
无论是正数还是负数,在移位之后,高位都补0。即移位之后永远都是正数。
注:

(1)无论是左移还是右移(包括无符号右移),都有一个共同的规则。 如果移动的位数超过规定的bit数,都会与最大移位数取模之后进行计算。

例如:
int型,32bits,如果是5<<33,其实就是5<<1;同理,右移和无符号右移也是一样。
那么对于long型数据,也是一样。5<<65其实就是5<<1。

(2)对于byte和short进行移位运算的时候,他们会被转换为int型。进行右移的时候,因为精度的原因(byte和short本身比int字节少,因此转成int计算完毕,再转换回去的时候,可能会对高位截断)
这个例子,可以参考《Think in Java》中的例子

如下:

package operators;

//: operators/URShift.java
// Test of unsigned right shift.
import static net.mindview.util.Print.print;

public class URShift {
  public static void main(String[] args) {
    int i = -1;
    print(Integer.toBinaryString(i));
    i >>>= 10;
    print(Integer.toBinaryString(i));
    long l = -1;
    print(Long.toBinaryString(l));
    l >>>= 10;
    print(Long.toBinaryString(l));
    short s = -1;
    print(Integer.toBinaryString(s));
    s >>>= 10;
    print(Integer.toBinaryString(s));
    byte b = -1;
    print(Integer.toBinaryString(b));
    b >>>= 10;
    print(Integer.toBinaryString(b));
    b = -1;
    print(Integer.toBinaryString(b));
    print(Integer.toBinaryString(b>>>10));
  }
} /* Output:
11111111111111111111111111111111
1111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
1111111111111111111111
*///:~

笔者的疑惑
对于上面的第二条,《Think in Java》并没有提到char,只是说byte和short位移的时候,可能出现这种意外。但是对于char型数据,
利用如下测试代码:

package baisc.bit;

public class BitCal {
    public static void main(String[] args) {
        char c1=(char)-1;
        System.out.println(Integer.toBinaryString(c1));
        c1>>=10;
        System.out.println(Integer.toBinaryString(c1));
        char c2=(char)-1;
        c2>>=17;
        System.out.println(Integer.toBinaryString(c2));
        char c3=(char)-1;
        c3>>=65;
        System.out.println(Integer.toBinaryString(c3));
        char u_c=(char)-1;
        u_c>>>=10;
        System.out.println(Integer.toBinaryString(u_c));
        short s=(short)-1;
        System.out.println(Integer.toBinaryString(s));
        s>>=33;
        System.out.println(Integer.toBinaryString(s));
    }
}

输出结果如下:

1111111111111111
111111
0
111111111111111
111111
11111111111111111111111111111111
11111111111111111111111111111111

所以,问题来了

1)为什么这里的第一个输出是16个1,而不是32个1,不是转换成int计算的么?
2)对于char类型的数据,右移和无符号右移都是高位补0么?

如果有读者了解这部分,希望能不吝赐教!

这里可能需要了解一下Integer类的toBinaryString()方法计算过程。

2.2 位与、或、异或、非

与:两个bit都为1的时候,结果为,否则为0
或:两个其中有一个为1即为1,否则为0
异或:相同为0,相异为1
非:0110

3 &和&&、|和||的异同

对于boolean类型的数据,&和&&,|和||的计算结果相同。但是这里有一处区别,按位与和按位或需要操作计算两个二进制的计算结果。但是如果是逻辑或,逻辑与,会有短路效应。可能只需要根据其中一个条件就可以判断了,例如true||false只需要判断前面一个就可以了,false&&false就只需要判断前面一个就知道为false。

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值