通过其它文章学习了Java左移和右移方面的知识,现在将几个博主写的文章和自己的构思进行整合起来,方便学习起来方便些。
一、原码、反码和补码
在开始java位运算的知识之前,我们先来了解几个基础的概念,原码,反码,补码。
1、原码
原码是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为0,负数该位为1,其余位表示数值的大小。
数值十进制5
[+5]=[00000101](原码)
[ - 5]=[10000101](原码)
2、反码
反码是数值存储的一种,但是由于补码更能有效表现数字在计算机中的形式,所以多数计算机一般都不采用反码表示数,反码的表示方法如下:
-
正数的反码是其本身
-
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
[+5]=[00000101](原码)= [00000101](反码)
[ - 5]=[10000101](原码)= [11111010](反码)
3、补码
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。补码的表示方法是:
正数的补码就是其本身
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
[+5]=[00000101](原码)= [00000101](反码)=[00000101](补码)
[ - 5]=[10000101](原码)= [11111010](反码)=[11111011](补码)
例如-5的反码为11111010,最后一位是0,通过在最后一位加1后,得到的补码为11111011。
如果遇到最后一位是1而不是0呢?
例如[-12] =[10001100](原码)= [11110011](反码)=[11110100](补码)
-12的反码11110011最后一位是1,加1后很容易发现二进制没有2这个数,只有0 、1 。二进制逢二进一就是当任何计算结果等于2的时候就要向前一位进1(完全从十进制类推)。进位的1加到相邻的高位上,相加的低位归零。
也就是1+1等于2后,最后一位归0,向前进一位(从右边向左边数起第二个数),向前进一位后发现也是1,继续归0,继续向前一位,此时数为0,0+1等于1,不等于2了,也就不用向前进位了。
最后的补码就是:11110100
[反码] 11110011
[补码] 11110100
小结
计算机中的符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。而在计算机系统中,数值一律用补码来表示和存储。
二、左移和右移
<<(左移),>>(右移)皆属于位运算符.其运算比乘除快,所以我们可以用其优化代码。
注:大家都知道在Java中int型占4个字节32位,以下数据类型使用int整型32位。
1、左移
<<表示左移,不分正负数,低位补0;
规则则是带符号位移,高位移出,低位补0,移动位数超过该类型的最大位数,则进行取模,如对Integer型左移34位,实际上只移动了两位。无溢出的前提下(左移未超出31位)左移一位相当于乘以2的一次方,左移n位相当于乘以2的n次方。
左移时不管正负,低位补0
示例一:正数20
20<<2;
20的32位二进制原码:00000000 00000000 00000000 00010100 (正数,最高位即第一位数符号位是0)
20的32位二进制反码:00000000 00000000 00000000 00010100 (正数的反码是其本身)
20的32位二进制补码:00000000 00000000 00000000 00010100 (正数的补码就是其本身)
正数的原码=反码=补码,一样的。
向左移动两位后的补码是:00 | 000000 00000000 00000000 00010100 00
| 前面被剔除两位(从左边开始剔除),右边后面缺少的两个补0(粗体0为补充的两个0)
左移两位后的补码为:00000000 00000000 00000000 01010000
高位符号位是0,说明结果是正数。由于正数的补码跟原码是一样的(补码=原码),这里就不用将补码转成原码,直接拿
00000000 00000000 00000000 01010000计算得到结果。
结果是80。
示例二:负数-20
-20<<2;
-20的32位二进制原码:10000000 00000000 00000000 00010100 (负数,最高位即第一位数符号位是1)
-20的32位二进制反码:11111111 11111111 11111111 11101011 (负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.)
-20的32位二进制补码:11111111 11111111 11111111 11101100 (负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1,遇到+1后的结果等于2时就逢二进一))
向左移动两位后的补码是:11 | 111111 11111111 11111111 11101100 00
| 前面被剔除两位(从左边开始剔除),右边后面缺少的两个补0(粗体0为补充的两个0)
左移两位后的补码为:11111111 11111111 11111111 10110000
负数的补码跟原码不一样,高位符号位是1,说明结果是负数。需要将补码转成原码。这里要计算结果前就需要将补码转换成原码了(补码转原码:符号位不变,数值位按位取反,末位再加1.即补码的补码等于原码)
转成原码后:10000000 00000000 00000000 01010000
结果是-80。
示例三:正数5
5<<29;
5的32位二进制原码:00000000 00000000 00000000 00000101 (正数,最高位即第一位数符号位是0)
5的32位二进制反码:00000000 00000000 00000000 00000101 (正数的反码是其本身)
5的32位二进制补码:00000000 00000000 00000000 00000101 (正数的补码就是其本身)
正数的原码=反码=补码,一样的。
向左移动29位后的补码是:00000000 00000000 00000000 00000 | 101 00000000000000000000000000000
| 前面被剔除29位(从左边开始剔除),右边后面缺少的补29个0(粗体0为补充的29个0)
左移29位后的补码为:10100000 00000000 00000000 00000000
正数5经过左移29位后的补码高位符号位是1,说明结果是负数。需要将补码转成原码。这里要计算结果前就需要将补码转换成原码了(补码转原码:符号位不变,数值位按位取反,末位再加1.即补码的补码等于原码)
转成原码后:11100000 00000000 00000000 00000000
结果是-1610612736。
示例四:正数2(超过31的都要继续取模)
2<<32; 结果等于2。因为32取模32后结果等于0,等于没有移位,所以结果等于它本身。
2<<33; 结果等于4。因为33取模32后结果等于1,只移动一位。
这里取模使用建议使用Math.floorMod(33, 32);因为取模与取余有区别的。
2、右移
>>表示右移,如果该数为正,则高位补0,若为负数,则高位补1;右移一位相当于除以2的一次方,右移n位相当于除以2的n次方,原来是正数的还是正数,负数还是负数。
示例一:正数20
20>>2;
20的32位二进制原码:00000000 00000000 00000000 00010100 (正数,最高位即第一位数符号位是0)
20的32位二进制反码:00000000 00000000 00000000 00010100 (正数的反码是其本身)
20的32位二进制补码:00000000 00000000 00000000 00010100 (正数的补码就是其本身)
正数的原码=反码=补码,一样的。
向右移动两位后的补码是:00 00000000 00000000 00000000 000101 | 00
| 后面被剔除两位(从右边开始剔除),前面(左边)缺少的两个补0,即正数高位补0(粗体0为补充的两个0)。
右移两位后的补码为:00000000 00000000 00000000 00000101
高位符号位是0,说明结果是正数。由于正数的补码跟原码是一样的(补码=原码),这里就不用将补码转成原码,直接拿
00000000 00000000 00000000 00000101算得到结果。
结果是5。
示例二:负数-20
-20>>2;
-20的32位二进制原码:10000000 00000000 00000000 00010100 (负数,最高位即第一位数符号位是1)
-20的32位二进制反码:11111111 11111111 11111111 11101011 (负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.)
-20的32位二进制补码:11111111 11111111 11111111 11101100 (负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1,遇到+1后的结果等于2时就逢二进一))
向右移动两位后的补码是:11 11111111 11111111 11111111 111011 | 00
| 后面被剔除两位(从右边开始剔除),前面(左边)缺少的两个补1,即负数高位补1(粗体1为补充的两个1)。
右移两位后的补码为:11111111 11111111 11111111 11111011
负数的补码跟原码不一样,高位符号位是1,说明结果是负数。需要将补码转成原码。这里要计算结果前就需要将补码转换成原码了(补码转原码:符号位不变,数值位按位取反,末位再加1.即补码的补码等于原码)
转成原码后:10000000 00000000 00000000 00000101
结果是-5。
3、无符号右移
>>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0
示例一:正数20
20>>>2;
结果跟20>>2一样。
示例二:负数-20
-20>>>2;
-20的32位二进制原码:10000000 00000000 00000000 00010100 (负数,最高位即第一位数符号位是1)
-20的32位二进制反码:11111111 11111111 11111111 11101011 (负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.)
-20的32位二进制补码:11111111 11111111 11111111 11101100 (负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1,遇到+1后的结果等于2时就逢二进一))
向右移动两位后的补码是:00 11111111 11111111 11111111 111011 | 00
| 后面被剔除两位(从右边开始剔除),前面(左边)缺少的两个补0。这里需要注意:右移后高位同样补0,而不是1。
右移两位后的补码为:00111111 11111111 11111111 11111011
高位符号位是0,说明结果是正数。由于正数的补码跟原码是一样的(补码=原码),这里就不用将补码转成原码,直接拿
00111111 11111111 11111111 11111011算得到结果。
结果是1073741819。
小结
左移或者右移移位后的补码如果符号位为1的,需要将补码转成原码(符号位为0的表示结果是正数,正数的补码和原码是一样的,不需要转换,可直接计算结果),才能计算得到结果。可使用例如下面代码传入一个正数的32位二进制字符串
/**
* 传入原码,2表示传入的二进制
*/
System.out.println(Integer.parseInt("00000000000000000000000000000101",2));
得到结果。如果是是负数的,例如负数-5的原码:10000000000000000000000000000101,可以将符号位1改成0,计算得到结果再转成负数。另外无符号右移该数无论是正数还是负数,它的结果都会为正数。如果要计算一个数值的二进制可以使用
/**
* 返回的是补码二进制,正数12返回1100(可以自行在高位补充0,让其够32位),
* 负数-12返回11111111111111111111111111110100
*/
System.out.println(Integer.toBinaryString(12));
参考
我们应该知道的java位运算_zejian_的博客-CSDN博客
Java中的左移、右移详细分析_The best are water的博客-CSDN博客_byte左移8位
一篇文章弄懂左移、右移位运算(Java)_DFYoung的博客-CSDN博客_右移32位相当于