计算机对有符号数(包括浮点数)的表示有三种方法:原码、反码和补码,补码=反码+1。在 二进制里,是用 0 和 1 来表示正负的,最高位为符号位,最高位为 1 代表负数,最高位为 0 代表正数。
在计算机中对数进行操作,移位操作可能比乘除操作更效率更高。比如,在ArrayList的扩容实现机制中,Java8中就将0.5倍由原来的除以2改为右移1位,以提高效率。
Java中负数的表示方法
在讲解移位操作之前,我们有必要了解一下Java中的负数表示方法。
绝对值取反码再加一
以Java中8位的byte为例,最大值为:01111111,最小值为1000 0000。
那么根据十进制的数字,我们如何转换为二进制呢?对于正数我们直接转换即可,对于负数则有一个过程。
以负数-5为例:
- 先将-5的绝对值转换成二进制,即为00000101;
- 然后求该二进制的反码,即为1111 1010;
- 最后将反码加1,即为:1111 1011
所以Java中Integer.toBinaryString(-5)结果为11111111 11111111 1111111111111011(Integer是32位(bit)的)
左移
正负数皆右补0
System.out.println(-3<<1);
左移相对来说比较简单.
1111 1111 1111 1111 1111 1111 1111 1101左移一位为
1111 1111 1111 1111 1111 1111 1111 1010,其为-6的补码.
System.out.println(Integer.MAX_VALUE<< 1);输出为-2
右移
正数左补0、负数左补1
System.out.println(-3>>1);
结果是-2,为什么会是-2呢?下面我们来看一下.
System.out.println(Integer.toHexString(-3));
得到-3的16进制为fffffffd(此为-3的补码,计算机中负数用补码表示).
转换成2进制为1111 1111 1111 1111 1111 1111 1111 1101
右移一位为1111 1111 1111 1111 1111 1111 1111 1110,显而易见此为-2补码.
无符号右移
全部左补0
System.out.println(-3>>>1);
1111 1111 1111 1111 1111 1111 1111 1101无符号右移,高位补0,
01111 1111 1111 1111 1111 1111 1111 1110,其为2147483646的原码.
为什么没有无符号左移?
因为左移是在后面补0,而右移是在前面边补1或0
有无符号是取决于数的前面的第一位是0还是1,所以右移是会产生到底补1还是0的问题。
而左移始终是在右边补,不会产生符号问题。所以没有必要无符号左移<<<。
无符号左移<<<和左移<<是一样的概念
Java中表示二进制、八进制、十进制、十六进制
Java里不能这样表示二进制,只能是 8,10,16进制
- 8进制:前置0
- 10进制:不需要前置
- 16进制:前置0x或者0X
为什么8位的二进制补码范围是[-128,127]
计算机对带符号数的表示有三种方法:原码、反码和补码
8位原码和反码能够表示数的范围是-127~127
8位补码能够表示数的范围是 -128~127
所以既然范围是-128~127,那肯定是用补码表示的。
10000000-11111111表示-128到-1, 00000000-01111111表示0-127
补码的1111 1111转换成原码就是1000 0001,也就是-1。
补码就是二进制表示负数的一种方法:
负数的补码就是对反码加一
正数不变,正数的原码反码补码是一样的
在补码中用(-128)代替了(-0),所以补码的表示范围为:(-128 ~ 0 ~ 127)共256个。
注意:(-128)没有相对应的原码和反码, (-128) = (10000000)
为了充分利用资源,就将原来本应该表示“-0”的补码规定为代表-128
所谓原码就是二进制定点表示法,即最高位为符号位,“0”表示正,“1”表示负,其余位表示数值的大小。
反码表示法规定:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。
补码表示法规定:正数的补码与其原码相同;负数的补码是在其反码的末位加1。