Java中的位运算
原码:用第一位表示符号,其余位表示值。
反码:正数的补码反码是其本身,负数的反码是符号位保持不变,其余位取反。
补码:正数的补码是其本身,负数的补码是在其反码的基础上+1。
正数: 原码=反码=补码
负数:反码=原码除符号位每位取反; 补码 = 反码+1
1、基本位运算
private static void println(String msg, int r){
System.out.println(msg + " ====> "+r);
}
public static void main(String[] args) {
//1、左移<<
println("5<<2", 5<<2);
/**
* 5<<2
* 原码:0000 0000 0000 0000 0000 0000 0000 0101
* 反码:0000 0000 0000 0000 0000 0000 0000 0101
* 补码:0000 0000 0000 0000 0000 0000 0000 0101
* 左移:0000 0000 0000 0000 0000 0000 0001 0100 (即补码)
* 补码:0000 0000 0000 0000 0000 0000 0001 0100
* 反码:0000 0000 0000 0000 0000 0000 0001 0100
* 原码:0000 0000 0000 0000 0000 0000 0001 0100
* 打印-结果:20
* 计算规则:1、向左移动指定位,左边二进制位丢弃,右边补0。(注意byte和short类型移位运算时会变成int型,结果要强制转换)
* 2、若1被移位到最左侧,则变成负数。
* 3、左移时舍弃位不包含1,则左移一次,相当于乘2。
**/
println("-5<<1",-5<<1);
/**
* -5<<1
* 原码:1000 0000 0000 0000 0000 0000 0000 0101
* 反码:1111 1111 1111 1111 1111 1111 1111 1010
* 补码:1111 1111 1111 1111 1111 1111 1111 1011
* 移位:1111 1111 1111 1111 1111 1111 1111 0110 (即补码)
* 补码:1111 1111 1111 1111 1111 1111 1111 0110
* 反码:1111 1111 1111 1111 1111 1111 1111 0101
* 原码:1000 0000 0000 0000 0000 0000 0000 1010
* 打印-结果:-10
*
**/
println("-5<<29",-5<<29); //PS:这样把0移到符号位,是不是就变成正数了?
/**
* -5<<29
* 原码:1000 0000 0000 0000 0000 0000 0000 0101
* 反码:1111 1111 1111 1111 1111 1111 1111 1010
* 补码:1111 1111 1111 1111 1111 1111 1111 1011
* 移位:0110 0000 0000 0000 0000 0000 0000 0000 (即补码)
* 补码:0110 0000 0000 0000 0000 0000 0000 0000
* 反码:0110 0000 0000 0000 0000 0000 0000 0000
* 原码:0110 0000 0000 0000 0000 0000 0000 0000
* 打印-结果:1610612736 正数!
* 2^30+2^29 = 1610612736
* 所以,同理,如果把正数的1 移到符号位,正数也变成了负数
**/
//------------------------------------分割线------------------------------------
//2、右移
println("5>>2", 5>>2);
/**
* 5>>2
* 原码:0000 0000 0000 0000 0000 0000 0000 0101
* 补码:0000 0000 0000 0000 0000 0000 0000 0101
* 反码:0000 0000 0000 0000 0000 0000 0000 0101
* 右移:0000 0000 0000 0000 0000 0000 0000 0001 (即补码)
* 补码:0000 0000 0000 0000 0000 0000 0000 0001
* 反码:0000 0000 0000 0000 0000 0000 0000 0001
* 原码:0000 0000 0000 0000 0000 0000 0000 0001
* 打印-结果:1
* 计算规则:1、数值value向右移动num位,正数左补0,负数左补1,右边舍弃。(即保留符号位)
* 2、右移一次,相当于除以2,并舍弃余数。
**/
//------------------------------------分割线------------------------------------
//3、无符号右移
println("-5>>>3",-5>>>3);
/**
* -5>>>3
* 原码:1000 0000 0000 0000 0000 0000 0000 0101
* 反码:1111 1111 1111 1111 1111 1111 1111 1010
* 补码:1111 1111 1111 1111 1111 1111 1111 1011
* 右移:0001 1111 1111 1111 1111 1111 1111 1111 (即补码)
* 补码:0001 1111 1111 1111 1111 1111 1111 1111
* 反码:0001 1111 1111 1111 1111 1111 1111 1111
* 原码:0001 1111 1111 1111 1111 1111 1111 1111
* 打印-结果:536870911
* 计算规则:1、数值value向右移动num位,正数左补0,负数左补1,右边舍弃。(即保留符号位)
* 2、右移一次,相当于除以2,并舍弃余数。
* 3、无符号右移>>>:左边位用0补充,右边丢弃。
* 所得结果永远正数
**/
//------------------------------------分割线------------------------------------
//4、位与
println("5 & 3",5 & 3);
/**
* 5 & 3
* 5 补码:0000 0000 0000 0000 0000 0000 0000 0101
* 3 补码:0000 0000 0000 0000 0000 0000 0000 0011
* 比较值:0000 0000 0000 0000 0000 0000 0000 0001
* 打印-结果: 1
* 计算规则:第一个操作数的的第n位于第二个操作数的第n位如果都是1,那么结果的第n为也为1,否则为0
**/
//------------------------------------分割线------------------------------------
//5、位或
println("5 | 3",5 | 3);
/**
* 5 | 3
* 5 补码:0000 0000 0000 0000 0000 0000 0000 0101
* 3 补码:0000 0000 0000 0000 0000 0000 0000 0011
* 结果:0000 0000 0000 0000 0000 0000 0000 0111
* 打印-结果:7
* 计算规则:第一个操作数的的第n位于第二个操作数的第n位 只要有一个是1,那么结果的第n为也为1,否则为0
**/
//------------------------------------分割线------------------------------------
//6、位异或
println("5 ^ 3",5 ^ 3);
/**
* 5 | 3
* 5 补码:0000 0000 0000 0000 0000 0000 0000 0101
* 3 补码:0000 0000 0000 0000 0000 0000 0000 0011
* 结果:0000 0000 0000 0000 0000 0000 0000 0110
* 打印-结果:6
* 计算规则:第一个操作数的的第n位于第二个操作数的第n位 相反,那么结果的第n为也为1,否则为0
**/
//------------------------------------分割线------------------------------------
//7、位非
println("~5",~5);
/**
* 5 补码:0000 0000 0000 0000 0000 0000 0000 0101
* 取非:1111 1111 1111 1111 1111 1111 1111 1010 (即补码)
* 补码:1111 1111 1111 1111 1111 1111 1111 1010
* 反码:1111 1111 1111 1111 1111 1111 1111 1001
* 原码:1000 0000 0000 0000 0000 0000 0000 0110
* 打印-结果:-6
* 计算规则:操作数的第n位为1,那么结果的第n位为0,反之。
**/
//------------------------------------分割线------------------------------------
}
2、取反运算~
2.1 计算1 : ~32 结果: -33
正数:32 二进制 原码为 0010 0000
1、计算机在保存数据时存的是补码 0010 0000 (正数的补码就是原码本身,负数的补码是原码按位取反再加1)
所以在进行位非运算时,直接进行计算
~32 按位取反 位非运算: 1101 1111
2、这个计算结果是计算后的补码形式,即计算机以补码的形式存储的内容,
由于负数的补码是按原码按位取反再加一
所以,要获得计算后的原码,我们需要先的到反码(及 补码减1)
~32 的反码为: 1101 1110
再求原码:~32 原码: 1010 0001 即 -33
所以可以知道,~32在计算机存储是按补码形式,~32是-33在计算机中的补码
2.2 计算2: ~(-32)结果为 31
负数:-32
1、 原码: 1010 0000
-32反码:1101 1111 (符号位不变,原码按位取反)
-32补码:1110 0000 (反码+1)
位非运算: 0001 1111
反码: 0001 1111
原码:0001 1111 (31)
整个计算过程可以总结为: 数字 x ->原码->反码->补码->(计算步骤)进行取反操作(获得~x在计算机存贮的值) 获得取反后补码->反码->原码->十进制
2.3 计算3: ~(-1L<<12L) 结果为 4095
3、PS:一个字节能存储的最大数是255
字节是存储器的基本单位,长度为8位,计算机存储器中数据都是以二进制保存的,每一位不是0就是1,所以8个二进制位全为1时,代表的数最大。二进制的11111111,转换为十进制就是255。
存储单元存储的数据和存储单元的数据类型有关,一般用一个字节存储的数据,其类型为正整数和0,此时一个字节能存储的最大数是255。如果一个字节存储的是字符数据,也可以用正整数和0来代表,如ASCII码就是用0-255的数字代表256个字符,此时说存储的最大数为255也是合理的。
4、Question:关于一些基本类型的取之范围疑问,int类型(16位)的下溢下限为-32768而上溢上限却是32767
上限:32767=2^15-1 (因为要有一个符号位)
下限:为什么不是 -2^15+1 ?
4.1 说法1:
MAX_INT + 1 = 1000 0000 0000 0000 0000 0000 0000 0000根据int有符号的定义,第一位是符号位,这样看来,这个数值就是 -0 ; 那么我们再来讨论这样一种场景,按照int的第一个位是符号位的说法 MIN_INT=1111 1111 1111 1111 1111 1111 1111 111,前面我们计算过 31位的全1 = 2^31 -1, 所以MIN_INT=-2^31 + 1. 这应该是正常思路看待int的最大值和最小值的场景.但是我们还有一个-0没有解决,我们按照之前定义的MIN_INT来计算一下MIN_INT-1 = -2^31 + 1 -1 = -2^31. 咦,我们去掉超过的1位,发现它竟然也是 1000 0000 0000 0000 0000 0000 0000 0000 和 -0的含义一样 ,所以我们要解决的-0 可以把原来的MIN_INT-1 作为新的MIN_INT! 这样int的取值范围就是 -2^31 到 2^31 -1
4.2 说法2:
网上的解释是:
有+0和-0,两个0。因为负数在计算机中都是以补码方式存储的,且没有任何正数的补码是 1000 0000 0000 0000 0000 0000 0000 0000 ,所以把 -0 看成 int最小值(符号位参与运算)
也就是说,为了解决冲突,类似于 补码中 1000 0000 表示 -128 (第一位符号位)其实是人为规定的