Java移位运算符的相关知识,包括其作用在负数上的情况。
Java语言规范
原文:The Java® Language Specification Java SE 8 Edition - 15.19. Shift Operators
The shift operators are syntactically left-associative (they group left-to-right).
Unary numeric promotion (§5.6.1) is performed on each operand separately. (Binary numeric promotion (§5.6.2) is not performed on the operands.)
It is a compile-time error if the type of each of the operands of a shift operator, after unary numeric promotion, is not a primitive integral type.
The type of the shift expression is the promoted type of the left-hand operand.
If the promoted type of the left-hand operand is int, then only the five lowest-order bits of the right-hand operand are used as the shift distance. It is as if the right-hand operand were subjected to a bitwise logical AND operator & (§15.22.1) with the mask value 0x1f (0b11111). The shift distance actually used is therefore always in the range 0 to 31, inclusive.
If the promoted type of the left-hand operand is long, then only the six lowest-order bits of the right-hand operand are used as the shift distance. It is as if the right-hand operand were subjected to a bitwise logical AND operator & (§15.22.1) with the mask value 0x3f (0b111111). The shift distance actually used is therefore always in the range 0 to 63, inclusive.
At run time, shift operations are performed on the two’s-complement integer representation of the value of the left operand.
The value of n << s is n left-shifted s bit positions; this is equivalent (even if overflow occurs) to multiplication by two to the power s.
The value of n >> s is n right-shifted s bit positions with sign-extension. The resulting value is floor(n / pow(2, s)). For non-negative values of n, this is equivalent to truncating integer division, as computed by the integer division operator /, by two to the power s.
The value of n >>> s is n right-shifted s bit positions with zero-extension, where:
If n is positive, then the result is the same as that of n >> s.
If n is negative and the type of the left-hand operand is int, then the result is equal to that of the expression (n >> s) + (2 << ~s).
If n is negative and the type of the left-hand operand is long, then the result is equal to that of the expression (n >> s) + (2L << ~s).
The added term (2 << ~s) or (2L << ~s) cancels out the propagated sign bit.
Note that, because of the implicit masking of the right-hand operand of a shift operator, ~s as a shift distance is equivalent to 31-s when shifting an int value and to 63-s when shifting a long value.
注释:原文中的
promotion
指的是类型转换,promoted type
指的是经过类型转换(包括隐式转换和显式转换)后的数据类型。
总结
根据Java规范对移位操作符的描述,可以总结为以下内容:
以n << s
或n >> s
或n >>> s
为例,其中n和s分别表示经过数据类型转换后(包括隐式转换和显式转换)的结果。
- n和s只能是基础的整数的数据类型,包括
byte
、short
、char
、int
、long
,不能是浮点型,否则会造成编译错误。 - 移位运算式的结果的数据类型与n的数据类型相同。
- 如果n的数据类型为int,那么只会截取s的二进制的低5位作为移位操作的距离,也就是说
n << s
等价于n << (s&0x1f)
,因此实际移位操作的距离范围为[0, 31]。 - 如果n的数据类型为long,同理,只会截取s的二进制的低6位作为移位操作的距离,也就是说
n << s
等价于n << (s&0x3f)
,实际移位操作的的距离范围为[0, 63]。 - 在运行时中,移位操作符的运算是在n的补码的基础上进行的。
n << s
表示n向左移s位,结果相当于n乘以2的s次方(即使移位操作溢出)。n >> s
表示n带符号向右移s位,结果为小于(n/(2的s次方))的最大整数(例如结果为2.5则取2,结果为-2.5则取-3)。若n为正数,可以认为在代码里执行n >> s
和执行n / Math.pow(2, s)
的结果是一样的,负数则不然。n >>> s
表示无符号右移s位,结果分为以下几种情况:- 如果n为正数,结果相当于
n >> s
。 - 如果n为负数,且n的数据类型为int,则其运算结果结果为
(n >> s) + (2 << ~s)
。 - 如果n为负数,且n的数据类型为long,则其运算结果结果为
(n >> s) + (2L << ~s)
。
- 如果n为正数,结果相当于
除了以上原文提到的,还需要知道几点:
- 在进行移位运算符之前,
byte
、short
、char
为被隐式转换为int
。 - 原文有提到n是以为补码的形式进行移位操作的,也就是说n在移位之前会先转换成补码。经过我的实际测试,发现n和s在移位操作之前都会先转换为补码。当然在一般情况下,n和s都是正数,在补码系统中,正数的表示和原码一样。
>>
在移位操作时会根据n的正负在高位进行补位,若n为正数,则每移动一位都会在高位补0,若n为负数,则每移动一位都会在高位补1。>>>
在移位操作时,不管n是正负都会在高位补0。
‘a’ << -10 = ?
|
|
以上这个比较奇怪的例子是可以编译通过的,并且执行结果为406847488。
根据这个例子,可以来详细了解移位运算符在执行时到底干了什么。
- 首先在执行移位操作之前,会检查移位运算符两边的操作数的数据类型,如果有必要的话会进行隐式转换。因此左边
char
类型的’a’会隐式转换为int
类型,即’a’会被转换成97,右边的-10为int
类型,不需要转换。 - 经过数据类型转换后,因为移位操作是在补码的基础上执行的,所以会将97和-10都先转换为补码,分别是97补码(0000 0000 0000 0000 0000 0000 0110 0001)和-10补码(1111 1111 1111 1111 1111 1111 1111 0110)。
- 接着,因为左边的97为
int
数据类型,因此会截取-10补码的低5位,即(10110),等于22。 - 现在可以开始移位操作了,将97补码(0000 0000 0000 0000 0000 0000 0110 0001)向左移22位(包括符号位),空位用0补全,结果为(0001 1000 0100 0000 0000 0000 0000 0000),注意此时得出的结果还是补码,因此还要将补码转换为原码才是最后的结果。这里的补码为正数,因此与原码一致,转换结果依旧是(0001 1000 0100 0000 0000 0000 0000 0000),等于406847488。
同理,>>
和>>>
执行过程也大致相同,只不过在移位时在高位补位的方式不同。
参考
The Java® Language Specification Java SE 8 Edition - 15.19. Shift Operators