最近在看 Doug Lea 大神写的并发编程的时候,经常会看到一些正负数的位运算和移位运算,以及一些符号等出现在代码中,不经勾起了对这些知识的回忆…
负数的二进制
计算机中的有符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两
部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同 。在计算机系
统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同
时,加法和减法也可以统一处理。
-x = !x+1 这个等式表示两边的二进制相等,x表示一个正整数,-x表示x的相反数的二进制,!x 表示x的二进制按位取反,然后+1
原码:正数的原码就是他的二进制,负数的原码就是他的绝对值的二进制的最高位补1。
反码:正数的反码与原码相同,负数的反码为他的原码除符号位外全部按位取反。
补码:正数的补码与原码相同,负数的补码为他的原码除符号位外全部按位取反,然后+1。
举例:
在java中,一个int占8字节,32bit,下面以32位二进制表示
1的原码:0000 0000 0000 0000 0000 0000 0000 0001
-1的原码:1000 0000 0000 0000 0000 0000 0000 0001
-1的反码:1111 1111 1111 1111 1111 1111 1111 1110
-1的补码:1111 1111 1111 1111 1111 1111 1111 1111
位运算
在 java 中,经常能看到代码中写到:&、|、~、^ 这几个符号,分别代表按位与、或、非、异或运算。
举例:
0100 & 1101 = 0100 同一位上都为1则返回1,从左至右,从高到低
0100 | 1101 = 1101 同一位上有一个为1则返回1
0100 ^ 1101 = 1001 同一位上互不相同则返回1
~1 = - 2
验证:
0000 0000 0000 0000 0000 0000 0000 0001 // 1的补码
1111 1111 1111 1111 1111 1111 1111 1110 // 1的补码取反
1的补码取反后高位为1,说明是负数,先减1,得到反码:
1111 1111 1111 1111 1111 1111 1111 1101
再取反,得到原码:1000 0000 0000 0000 0000 0000 0000 0010
其代表10进制数为-2,所以 ~1 = -2
移位运算
左移(<<):按二进制形式把所有的数字向左移动对应的位数,高位移出(舍弃),低位的空位补零。在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方。例:1 << 2,0001 》0100 = 4
右移(>>):按二进制形式把所有的数字向右移动对应位移位数,低位移出(舍弃),高位的空位补符号位,即正数补零,负数补1。右移一位相当于除2,右移n位相当于除以2的n次方。4 >> 2,0100 》 0001 = 1
无符号右移(>>>):按二进制形式把所有的数字向右移动对应位数,低位移出(舍弃),高位的空位补零。对于正数来说和带符号右移相同,对于负数来说不同。其他和右移相似。
-4 >>> 2 = 1073741823
推算过程:
0000 0000 0000 0000 0000 0000 0000 0100 // 4的原码
1000 0000 0000 0000 0000 0000 0000 0100 // -4的原码
1111 1111 1111 1111 1111 1111 1111 1011 // -4的反码
1111 1111 1111 1111 1111 1111 1111 1100 // -4的补码
0011 1111 1111 1111 1111 1111 1111 1111 // -4的补码无符号右移2位
// 0011 1111 1111 1111 1111 1111 1111 1111
// 等于 1*2^29 + 1*2^28 + ... + 1*2^0 ,所以初始值设置为 1*2^29 = 1 << 29
public static void main(String[] args) {
Integer num = 1 << 29;
Integer total = num;
for (int i=28; i>=0; i--) {
num = num >> 1;
total += num;
}
System.out.println(total);
System.out.println(-4 >>> 2);
}