Java数字位运算

目录

1.  Java虚拟机整数

1.1. Java原码、补码、反码

1.1.1.   原码

1.1.2.   反码

1.1.3.   补码

1.1.4.    总结

1.1.5.    Java byte 类型的取值范围说明

1.1.6.   常见问题

1.1.7.    为何使用补码

1.2. 位移操作

1.2.1.    <<左移

1.2.2.    >>右移

1.2.3.    >>> 无符号右移 

1.2.4.    位移操作n为负数说明

1.3. 位运算

1.3.1.    ~(按位非)

1.3.2.    | (按位或)

1.3.3.    &(按位与)

1.3.4.    ^ (按 位异或)


1.  Java虚拟机整数

在java虚拟机中整数有byte、short、int、long四种 分别表示 8位、16位、32位、64位有符号整数。

整数使用补码表示。

1.1. Java原码、补码、反码

1.1.1.   原码

原码就是符号位加上数字绝对值的二进制表示, 即用第一位表示符号(0正数 1负数), 其余位表示值, 比如以8位(一个字节)表示7的二进制:

[+7]原 = 00000111

[-7]原 = 10000111

原码是人脑最容易理解和计算的表示方式. 对于原码来说,绝对值相等的正数和负数只有符号位不同。

1.1.2.   反码

反码的表示方法是:

正数的反码是其本身

负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.

[+7] = [00000111]原 = [00000111]反

[-7] = [10000111]原 = [11111000]反

可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算.

1.1.3.   补码

补码的表示方法是:

正数的补码就是其本身

负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

[+7] = [00000111]原 = [00000111]反 = [00000111]补

[-7] =  [10000111]原 = [11111000]反 = [11111001]补

对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值. 

1.1.4.    总结

正数:它的原码、反码、补码相同。

负数:

反码:在原码基础上符号位不变化,其余位数取反,

补码:负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

1.1.5.    Java byte 类型的取值范围说明

最大值:0111 1111,即127

最小值:1000 0000 ,减去1是 1111 1111 按照位取反 1000 0000 得到-128

1.1.6.   常见问题

int a=232

//0000 0000 1110 1000

System.out.println(Integer.toBinaryString(a));

System.out.println((byte) a);

输出结果为-24:

1) 232补码(原码一样):0000 0000 0000 0000 0000 0000 1110 10000

2) 强转Byte(8位数字)后补码为 :1110 1000, 对应的数字原码 :先减1->1110 0111,再取反->1001 1000 为结果的原码即-24

1.1.7.    为何使用补码

现在我们知道了计算机可以有三种编码方式表示一个数. 对于正数因为三种编码方式的结果都相同:

[+1] = [00000001]原 = [00000001]反 = [00000001]补

所以不需要过多解释. 但是对于负数:

[-1] = [10000001]原 = [11111110]反 = [11111111]补

可见原码, 反码和补码是完全不同的. 既然原码才是被人脑直接识别并用于计算表示方式, 为何还会有反码和补码呢?

首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对值区域的加减. 但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单. 计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了.

于是人们开始探索 将符号位参与运算, 并且只保留加法的方法. 首先来看原码:

计算十进制的表达式: 1-1=0

1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2

如果用原码表示, 让符号位也参与计算, 显然对于减法来说, 结果是不正确的.这也就是为何计算机内部不使用原码表示一个数.

为了解决原码做减法的问题, 出现了反码:

计算十进制的表达式: 1-1=0

1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0

发现用反码计算减法, 结果的真值部分是正确的. 而唯一的问题其实就出现在"0"这个特殊的数值上. 虽然人们理解上+0和-0是一样的, 但是0带符号是没有任何意义的. 而且会有[0000 0000]原和[1000 0000]原两个编码表示0.

于是补码的出现, 解决了0的符号以及两个编码的问题:

1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原

这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128:

(-1) + (-127) = [1000 0001]原+ [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补

-1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的)

使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].

因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-231,-231 -1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值.

1.2. 位移操作

位移操作:(只针对 int类型的数据有效,java中,一个int的长度始终是32位,也就是4个字节,它操作的都是该整数的二进制数).也可以作用于以下类型,即 byte,short,char,long(当然,它们都是整数形式)。当为这四种类型是,JVM先把它们转换成int型再进行操作。

<<     左移  

>>     右移

>>>    无符号右移

【注】:Java中不存在<<<

1.2.1.    <<左移

  m<(含符号位)移出n位都舍弃,低位补0. (此时将可能会出现正数变成负数的形式)

实例:

1) 3<<2剖析:

3二进制形式: 00000000 0000000000000000 00000011,

按照位移得到: 00000000 0000000000000000 00001100,即12.     

2) 左移使整数变为负数:

 10737418<<8

10737418二进制表示形式:

00000000 10100011 11010111 00001010,

位移得到

10100011 11010111 00001010 00000000,即为:-1546188288.

1.2.2.    >>右移

m>>n的含义:把整数m表示的二进制数右移n位,m为正数,高位全部补0;m为负数,高位全部补1.

 实 例: 

1) 3>>2剖析:

3二进制形式: 00000000 00000000 0000000000000011,位移后,得到             00000000 00000000 0000000000000000,即为0.

2) -3>>2剖析:

      -3二进制形式: 11111111 1111111111111111 11111101,

      位移后得到    1111111111111111 11111111 11111111,即为-1.

以上:每个整数表示的二进制都是32位的,如果右移32位和右移0位的效果是一样的。依次类推,右移32的倍数位都一样。

备注:对于右移32位与右移0位是结果是一样的,我一直不能够理解。现在我只能理解为32比较特殊。相当于整体全移。与移0位相同。左移也是一样的。

1.2.3.    >>> 无符号右移 

m>>>n:整数m表示的二进制右移n位,不论正负数,高位都补零。

实例:

1) 3>>>2剖析:

 3二进制形式:00000000 00000000 00000000 00000011, 

位移得到:  00000000 00000000 00000000 00000000,即0.

2) -3>>>2剖析:

   -3二进制形式:  11111111 1111111111111111 11111101,
     位移后得到:      00111111 11111111 11111111 11111111,即为1073741823.

1.2.4.    位移操作n为负数说明

对于1.2.1,1.2.2,1.2.3,如果n为负数:这时JVM会先让n对32取模,变成一个绝对值小于32的负数,然后再加上32,直到 n 变成一个正数。

实例:

4<<-10

4的二进制形式:00000000 00000000 0000000000000100,-10对32取模再加上32,得到22,则4<<-10,即相当于4<<22,

   此时位移,得到00000001 00000000 0000000000000000,得到的即为:16777216。

综上所述:

m<即在数字没有溢出的前提下,对于正数和负数,左移n位都相当于m乘以2n次方.

m>>n即相当于m除以2n次方,得到的为整数时,即为结果。如果结果为小数,此时会出现两种情况:(1)如果m为正数,得到的商会无条件的舍弃小数位;(2)如果m为负数,舍弃小数部分,然后把整数部分加+1得到位移后的值。

接下来在此说说位操作的好处,速度超快,这些都是底层的二进制机器操作指令。

比如:a*2

1.    jvm先为变量a分配空间;

2.    再进行a*2的操作;

3.    再把结果返回给相应的变量。

a<<1,a*2一样,它只需要一条指令即可,速度很快。当然前三种位移操作都是对2的倍数进行操作时可用。

1.3. 位运算

1.3.1.    ~(按位非)

【解义】一元操作符 ,对该整数的二进制形式逐位取反。

~4:(一元操作符)
     4的二进制形式为:00000000 00000000 0000000000000100,

逐位取反后得到:  11111111 11111111 11111111 11111011,即为-5.

1.3.2.    | (按位或)

     【解义】对两个整数的二进制形式逐位进行逻辑或运算,原理为:1|0=1,0|0=0,1|1=1,0|1=1 等。

 4 |-5:

4的二进制形式为:00000000 00000000 00000000 00000100,

-5的二进制形式为:11111111 11111111 11111111 11111011,

逐位进行逻辑或运算: 11111111 1111111111111111 11111111,即得到-1.

1.3.3.    &(按位与)

【解义】对两个整数的二进制形式逐位进行逻辑与 运算,原理:1&0=0,0&0=0,1&1=1;0&1=0等。

4&-5:

4的二进制形式为:00000000 00000000 00000000 00000100

-5的二进制形式为:11111111 11111111 11111111 11111011

逐位进行逻辑与运算:00000000 00000000 0000000000000000,即得到0.  

1.3.4.    ^ (按 位异或)

【解义】对两个整数的二进制形式逐位进行逻辑异或运算,原理:1^1=0,1^0=1,0^1=1,0^0=0.

4^-5:

4的二进制形式为:00000000 00000000 00000000 00000100,

-5的二进制形式为:11111111 11111111 11111111 11111011,

逐位进行逻辑异或运算:11111111 1111111111111111 11111111,即得到-1.

实际应用:可以把字节转换为整数,-64&0xFF=192,也可以用八进制的形式,-64&0377=192

其实0xFF0377都表示的是整数255

实际应用:按位异或可以比较两个数字是否相等,它利用 1^1=0,0^0=0的原理。 20^20==0

 

你可能感兴趣的:(Java,基础理论,数字运算)