📝 摘要:在众多语言中,都提供了关于位操作符,但 为什么右移有“有无”符号之别,而左移却没有?,本文一步步讲述左移、有符号右移、无符号右移,以此来揭开位移的面纱,最终探究答案,囿于本人知识与能力,如有纰漏,敬请指出!
1. 引言
在 JavaScript 和 Java 语言中,针对二进制进行移位的位操作符有:左移 <<
、有符号右移 >>
、无符号右移 >>>
,首先我们先从概念上去剖析它们,这里引用《计算机组成原理教程》书中的一段描述:
计算机中机器数的字长往往是固定的,当机器数左移 n 位或右移 n 位时,必然会使其 n 位低位或 n 位高位出现空位。那么,对空出的空位应该添补 0 还是 1 呢?这与机器数采用有符号数还是无符号数有关。对无符号数的移位称为逻辑移位,对有符号数的移位称为算术移位。
考虑符号位的位操作都称为算数移位,“算数”,就是用于计算所用;不考虑符号位的都成为逻辑移位,“逻辑”,就是简单的移位,一般用于压缩编码等单纯移位,不需要考虑符号位。以这两个概念作为分类可将移位的位操作符分为:
分类 | 左移 | 右移 |
---|---|---|
算数移位 | 左移<< | 有符号右移符号>> |
逻辑移位 | 无符号右移符号>>> |
看完这个分类表,对强迫症来说似乎不那么友好,这也就引出来本博客的题目:为什么右移有“有无”符号之别,而左移却没有?
回答这个问题之前,还是得了解移位操作,为了简化描述,我们以 5 位二进制数(最高位为符号位)来作为举例:
2. 左移
对于数值 3
,它的二进制位 00011
,左移前可以表示如下:
下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
位数 | 0 | 0 | 0 | 1 | 1 |
左移两位 3 << 2
,注意符号位不参与移位
下标 | 4 | 3 | 2 | 1 | 0 | ||
---|---|---|---|---|---|---|---|
位数 | 0 | 0 | 0 | 1 | 1 |
高位舍弃,低位补 0,结果可得 12
下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
位数 | 0 | 1 | 1 | 0 | 0 |
对于 -3
,左移前可以表示如下:
下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
位数 | 1 | 1 | 1 | 0 | 1 |
在计算机底层中,负数以补码的形式存储,正数的补码和原码一样,负数的补码由除符号位的原码(该数的绝对值)的反码再加一可得
左移两位 -3 << 2
,注意符号位不参与移位
下标 | 4 | 3 | 2 | 1 | 0 | ||
---|---|---|---|---|---|---|---|
位数 | 1 | 1 | 1 | 0 | 1 |
高位舍弃,低位补 0,结果可得 -12
(注意结果仍为补码)
下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
位数 | 1 | 0 | 1 | 0 | 0 |
这里结果的计算涉及到原码和补码的转换,可以参考博客:补码和原码的转化过程
3. 有符号右移
为了更好的举例,这里我们使用数值 5
,它的二进制位 00101
,有符号右移前可以表示如下:
下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
位数 | 0 | 0 | 1 | 0 | 1 |
有符号右移两位5 >> 2
,注意符号位不参与移位
下标 | 4 | 3 | 2 | 1 | 0 | ||
---|---|---|---|---|---|---|---|
位数 | 0 | 0 | 1 | 0 | 1 |
高位补符号位,低位舍弃,有符号右移结果为 1
下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
位数 | 0 | 0 | 0 | 0 | 1 |
注意!!!,高位是补符号位,正数补 0,看不出效果,当对于负数来说,却又天壤之别。
对于 -5
,有符号右移前可以表示如下:
下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
位数 | 1 | 1 | 0 | 1 | 1 |
有符号右移两位 -5 >> 2
,注意符号位不参与移位
下标 | 4 | 3 | 2 | 1 | 0 | ||
---|---|---|---|---|---|---|---|
位数 | 1 | 1 | 0 | 1 | 1 |
高位补符号位,低位舍弃,结果为 -2
下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
位数 | 1 | 1 | 1 | 1 | 0 |
4. 无符号右移
无符号右移把整个数当作无符号数来看待,再进行移动
对于 5
,无符号右移前可以表示如下:
下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
位数 | 0 | 0 | 1 | 0 | 1 |
无符号右移两位 5 >>> 2
下标 | 4 | 3 | 2 | 1 | 0 | ||
---|---|---|---|---|---|---|---|
位数 | 0 | 0 | 1 | 0 | 1 |
高位补 0,低位舍弃,无符号右移结果为 1
下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
位数 | 0 | 0 | 0 | 0 | 1 |
对于 -5
,无符号右移前可以表示如下:
下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
位数 | 1 | 1 | 0 | 1 | 1 |
无符号右移两位 -5 >>> 2
,注意,此时将整个数当作无符号数来看待
下标 | 4 | 3 | 2 | 1 | 0 | ||
---|---|---|---|---|---|---|---|
位数 | 1 | 1 | 0 | 1 | 1 |
高位补 0,低位舍弃,此时结果变为正数 6
,
下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
位数 | 0 | 0 | 1 | 1 | 0 |
5. 总结
- 我们可以看到,对于正数,有符号右移和无符号右移都是一样的,但是对于负数,且大相径庭,原值
-5
,无符号右移后变成了6
。 - 我们再以宏观的视角去对比完左移和右移,就会发现,右移后高位是空出来的,对于空出来的高位,就需要去考虑补 0 还是补 1,而左移后高位时被舍弃掉的,根本不需要“补位”一说。这也就解释了为什么右移有“有无”符号之别,而左移却没有,因为根本就没有必要。
6. 参考资料
[1] 千万别小看这些运算符背后的逻辑:
https://developer.aliyun.com/article/763951:
[2] 补码和原码的转化过程:https://www.cnblogs.com/wxf0701/archive/2008/08/14/1267639.html