前言:现在的计算机底层使用二进制进行运算(0,1),一个数值有了原码,为什么还要反码,补码;当java 中使用&,|,^ ,<<,<<<,>>,>>>运算符时又是怎么进行运算的;

1 概念:
原码:
计算机中一个数字如果用2进制进行表示,我们知道2进制位数的最高位是符号位,0代表正数,1代表负数;
如 8位2进制中1 的原码为: 0000 0001; -1 的原码为:1000 0001
因为第一位是符号位,所以8位2进制数据标识的范围就是[1111 1111 , 0111 1111] 即:[-127 , 127]

反码:
正数的反码是其本身,如 1 的原码为 0000 0001,其反码: 0000 0001
负数的反码是在原码的基础上,符号位不变,其余个位安位取反(即0 ->1,1->0)如-1 的原码为 1000 0001,其反码1111 1110

补码:
正数的补码是其本身 ,如 1 的原码为 0000 0001,其补码: 0000 0001
负数的补码是在原码的基础上,最高符号位保持不变,其余个位安位取反,末位+1 如-1 的原码为 1000 0001,其补码1111 1111

既然有了原码为什么还要反码和补码:
计算机的底层是通过2进制数完成的,在我们看来原码不管多少位,高位都是符号位(0为正数,1为负数),如果机器使用原码进行运算需要对符号位进行特殊的处理;
如果用原码进行计算
1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2
那么使用反码可以吗:
1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
此时值是正确的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:
32位int类型, 可以表示范围是: [-2^31, 2^31-1] 在原码中第一位表示的是符号位,而使用补码表示时又可以多保存一个最小值。

tip :
1 正数的原码=反码=补码
2 负数: 反码的反码即为原码;补码的补码是原码

2 运算:
在计算机中对于数值的计算都是使用补码来完成;
2.1 “&”:与运算符
“&”运算符,就是按位“与”, 只有两者对应位都为“1”,结果对应位才为 1,否则为 0
1 0 1 0
1 1 0 0
= 1 0 0 0

2.2 “|”:或运算符
“|”运算符,就是按位“或”,只要两者对应位有一个为“1”,则结果对应位就为 1,否则为 0
1 0 1 0
1 1 0 0
= 1 1 1 0

2.3 “^”:异或运算符
“^”运算符,就是按位“异或”,只有两者对应位不一样(即一个为 1, 一个为 0 ),则结果对应位才为 1, 否则为 0
1 0 1 0
1 1 0 0
= 0 1 1 0

2.4 “~”:取反运算符
“~”运算符,就是按位“取反”,如果该位为 1,则结果对应位为 0,如果该位为 0, 则结果对应位为 1
~ 1 0 1 0
= 0 1 0 1

3 左移和右移:
3.1 左移运算符:
左移运算符<<使指定值的所有位都左移规定的次数。
1)它的通用格式如下所示:
value << num
num 指定要移位值value 移动的位数。
左移的规则只记住一点:丢弃最高位,0补最低位
如果移动的位数超过了该类型的最大位数,那么编译器会对移动的位数取模。如对int型移动33位,实际上只移动了332=1位。
2)运算规则
按二进制形式把所有的数字向左移动对应的位数,高位移出(舍弃),低位的空位补零。
当左移的运算数是long 类型时,每移动1位它的第63位就要被移出并且丢弃。
当左移的运算数是byte 和short类型时,将自动把这些类型扩大为 int 型。
3)数学意义
在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方
4)计算过程:
例如:3 <<2(3为int型)
1)把3转换为二进制数字0000 0000 0000 0000 0000 0000 0000 0011,
2)把该数字高位(左侧)的两个零移出,其他的数字都朝左平移2位,
3)在低位(右侧)的两个空位补零。则得到的最终结果是0000 0000 0000 0000 0000 0000 0000 1100,
转换为十进制是12。
移动的位数超过了该类型的最大位数,
如果移进高阶位(31或63位),那么该值将变为负值。

3.2 右移运算符
右移运算符<<使指定值的所有位都右移规定的次数。
1)它的通用格式如下所示:
value >> num
num 指定要移位值value 移动的位数。
右移的规则只记住一点:符号位不变,左边补上符号位
2)运算规则:
按二进制形式把所有的数字向右移动对应的位数,低位移出(舍弃),高位的空位补符号位,即正数补零,负数补1
当右移的运算数是byte 和short类型时,将自动把这些类型扩大为 int 型。
例如,如果要移走的值为负数,每一次右移都在左边补1,如果要移走的值为正数,每一次右移都在左边补0,这叫做符号位扩展(保留符号位)(sign extension ),在进行右移操作时用来保持负数的符号。
3)数学意义
右移一位相当于除2,右移n位相当于除以2的n次方。
4)计算过程
11 >>2(11为int型)
1)11的二进制形式为:0000 0000 0000 0000 0000 0000 0000 1011
2)把低位的最后两个数字移出,因为该数字是正数,所以在高位补零。
3)最终结果是0000 0000 0000 0000 0000 0000 0000 0010。
转换为十进制是2。
35 >> 2(35为int型) 35转换为二进制:0000 0000 0000 0000 0000 0000 0010 0011
低位的最后两个数字移出:0000 0000 0000 0000 0000 0000 0000 1000 转换为十进制: 8
5)在右移时不保留符号的出来
右移后的值与0x0f进行按位与运算,这样可以舍弃任何的符号位扩展,以便得到的值可以作为定义数组的下标,从而得到对应数组元素代表的十六进制字符。

总结:
  对于带符号右移,若为负数,则在存储时首位表示符号位,其值为1,表示该值是负数的移位,在移位过程中,高位补1,若符号位是0,表示是正数,在移位过程中高位补零,两者的前提是符号位保持不变:
对于负数的右移:因为负数在内存中是以补码形式存在的,所有首先根据负数的原码求出负数的补码(符号位不变,其余位按照原码取反加1),然后保证符号位不变,其余位向右移动到X位,在移动的过程中,高位补1.等移位完成以后,然后保持符号位不变,其余按位取反加1,得到移位后所对应数的原码。即为所求。

3.3 无符号右移:
无符号右移运算符>>>
它的通用格式如下所示:
value >>> num
num 指定要移位值value 移动的位数。
无符号右移的规则只记住一点:忽略了符号位扩展,0补最高位
无符号右移规则和右移运算是一样的,只是填充时不管左边的数字是正是负都用0来填充,无符号右移运算只针对负数计算,因为对于正数来说这种运算没有意义,无符号右移运算符>>> 只是对32位和64位的值有意义.

参考: java中的左移 右移