Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

重学js —— 按位移位运算符 #74

Open
lizhongzhen11 opened this issue Feb 21, 2020 · 1 comment
Open

重学js —— 按位移位运算符 #74

lizhongzhen11 opened this issue Feb 21, 2020 · 1 comment
Labels
js基础 Good for newcomers 重学js 重学js系列 规范+MDN

Comments

@lizhongzhen11
Copy link
Owner

lizhongzhen11 commented Feb 21, 2020

按位移位运算符

  • MDN
  • MDN——表达式和运算符
  • 对于非负数,有符号右移和无符号右移总是返回相同的结果。
  • 求负数的二进制编码
    1. 先求其正数的二进制编码
    2. 对编码进行取反,得到反码
    3. 对反码 + 1,得到补码,即该负数的编码
  • 右移时,左边最高位补0代表该数为正,补1代表该数为负

一、左移运算符 (<<)

MDN解释

移位表达式 << 加法表达式

0 << 1 // 0 --> 0 * (2 ** 1)
1 << 1 // 2 --> 1 * (2 ** 1)
2 << 1 // 4 --> 2 * (2 ** 1)
3 << 1 // 6 --> 3 * (2 ** 1)
4 << 1 // 8 --> 4 * (2 ** 1)
-1 << 1 // -2 --> -1 * (2 ** 1)
1 << 10 // 1024 --> 1 * (2 ** 10)
true << 1 // 1 --> 1 * (2 ** 1)
false << 1 // 0 --> 0 * (2 ** 1)
null << 1 // 0
undefined << 1 // 0
NaN << 1 // 0
Infinity << 1 // 0
-Infinity << 1 // 0
[] << 1 // 0
[{}] << 1 // 0
9n << 1 // 报错
Number(9n) << 1 // 18 --> 9 * (2 ** 1)
'' << 1 // 0 --> 0 * (2 ** 1) 先把字符串转为Number

左移运算,把运算符右边的数值看成 幂(也是数学上常说的次方),基数是 2,可以看成 运算符左边的数值 * (2 ** 运算符右边的数值)。运算符左边会先转换成数值,注意这里不是简单的 ToNumber,左边执行的是 ToInt32,所以undefined之类能转成0。

运算符右边如果不是BigInt,那么会执行 ToUint32 转换

  1. 定义 lref移位表达式 运算结果
  2. 定义 lval? GetValue(lref) 结果
  3. 定义 rref加法表达式 运算结果
  4. 定义 rval? GetValue(rref) 结果
  5. 定义 lnum? ToNumeric(lval)
  6. 定义 rnum? ToNumeric(rval)
  7. 如果 lnumrnum 的类型不同,抛 TypeError 异常
  8. 定义 Tlnum 的类型
  9. 返回 T::leftShift(lnum, rnum) (PS:有可能是 BigInt 类型的 ::leftShift

二、带符号的右移运算符 (>>)

MDN解释

移位表达式 >> 加法表达式

0 >> 0 // 0
0 >> 1 // 0
99 >> 0 // 99
1 >> 1 // 0
2 >> 1 // 1
3 >> 1 // 1
4 >> 1 // 2
1 >> 2 // 0
2 >> 2 // 0
3 >> 2 // 0
4 >> 2 // 1
8 >> 2 // 2
true >> 0 // 1
true >> 1 // 0
false >> 1 // 0
null >> 1 // 0
undefined >> 1 // 0
NaN >> 1 // 0
'' >> 1 // 0
'adadfsfs' >> 1 // 0
[] >> 1 // 0
[{}] >> 1 // 0
Number(2n) >> 1 // 1
Infinity >> 1 // 0
-Infinity >> 1 // 0
-9 >> 1 // -5
9 >> 1 // 4

运算符左边如果不是BigIng,会执行 ToInt32 转换;运算符右边如果不是BigInt,那么会执行 ToUint32 转换

  1. 定义 lref移位表达式 运算结果
  2. 定义 lval? GetValue(lref) 结果
  3. 定义 rref加法表达式 运算结果
  4. 定义 rval? GetValue(rref) 结果
  5. 定义 lnum? ToNumeric(lval)
  6. 定义 rnum? ToNumeric(rval)
  7. 如果 lnumrnum 的类型不同,抛 TypeError 异常
  8. 定义 Tlnum 的类型
  9. 返回 T::signedRightShift(lnum, rnum) (PS:也可能是 BigInt 类型的 ::signedRightShift

这里懂点需要点二进制知识,以上面示例代码 2 >> 1 为例:

// 2 在运算符左边,执行 ToInt32(2) 转换成32位有符号二进制整数,对应上述算法的 lnum,即
00000000 00000000 00000000 00000010
// 1 在运算符右边,执行 ToUint32(1) 转换成32位无符号二进制整数,对应上述算法的 rnum,即
00000000 00000000 00000000 00000001
// 根据 Number::signedRightShift 算法 执行 rnum & 0x1F 按位与运算,即
00000000 00000000 00000000 00000001 & 00000000 00000000 00000001 11111111
// 得到 shiftCount 二进制形式
00000000 00000000 00000000 00000001
// 然后对 lnum 右移 shiftCount 位,其实就是右移 1 位,
// 对 00000000 00000000 00000000 00000010 右移一位,最右边的一位这里是0,丢掉,左边最高位补个0,得到
00000000 00000000 00000000 00000001
// 最终转换为十进制,得到
1

为何 9 >> 14,而 -9 >> 1-5 呢?为何不是 -4

// 先对 -9 进行二进制转换,这里需要转换成有符号的32位二进制整数
// 先转 9,得到
00000000 00000000 00000000 00001001
// 取其反码
11111111 11111111 11111111 11110110
// + 1,得到 -9 对应的32位有符号二进制数
11111111 11111111 11111111 11110111
// 然后右移 1 位,得到
11111111 11111111 11111111 11111011
// 这是补码形式,我需要转换成其正数形式得到对应的十进制整数,
// 那么此时要先 - 1 得到反码
11111111 11111111 11111111 11111010
// 反码转换成正常码
00000000 00000000 00000000 00000101
// 对应十进制正数 5,但是其实我得到的是负数,只是为了获取十进制所以进行了逆推
// 所以实际上最终结果是
-5

三、无符号右移运算符 (>>>)

MDN解释

移位表达式 >>> 加法表达式

0 >>> 0 // 0
0 >>> 1 // 0
1 >>> 0 // 1
2 >>> 0 // 2
1 >>> 1 // 0
2 >>> 1 // 1
3 >>> 1 // 1
4 >>> 1 // 2
5 >>> 1 // 2
6 >>> 1 // 3
1 >>> 2 // 0
2 >>> 2 // 0
4 >>> 2 // 1
true >>> 0 // 1
true >>> 1 // 0
false >>> 0 // 0
null >>> 0 // 0
null >>> 1 // 0
undefined >>> 0 // 0
undefined >>> 1 // 0
NaN >>> 1 // 0
'' >>> 1 // 0
'adadfsfs' >>> 1 // 0
[] >>> 1 // 0
[{}] >>> 1 // 0
Number(2n) >>> 1 // 1
Infinity >>> 1 // 0
-Infinity >>> 1 // 0
-1 >>> 0 // 4294967295
-1 >>> 1 // 2147483647

运算符左边如果不是BigIng,会执行 ToInt32 转换;运算符右边如果不是BigInt,那么会执行 ToUint32 转换

  1. 定义 lref移位表达式 运算结果
  2. 定义 lval? GetValue(lref) 结果
  3. 定义 rref加法表达式 运算结果
  4. 定义 rval? GetValue(rref) 结果
  5. 定义 lnum? ToNumeric(lval)
  6. 定义 rnum? ToNumeric(rval)
  7. 如果 lnumrnum 的类型不同,抛 TypeError 异常
  8. 定义 Tlnum 的类型
  9. 返回 T::unsignedRightShift(lnum, rnum) (PS:也可能是 BigInt 类型的 ::unsignedRightShift

为何 -1 >>> 0 结果是 4294967295

// 首先明确这里是无符号右移运算,32位无符号整数范围是 0 ~ (2 ** 32) - 1
// 1 对应的码是
00000000 00000000 00000000 00000001
// 其反码
11111111 11111111 11111111 11111110
// 其补码
11111111 11111111 11111111 11111111
// 右移 0 位,按道理是
11111111 11111111 11111111 11111111
// 可是这里不能为负,只能是正数,这个数刚好是 (2 ** 32) - 1 即
4294967295

// -1 >>> 1 右移1位,由于无符号右移,
// 所以最高位只能补0,不能补1,
// 最高位补 0 为正,最高位补 1 为负
// 所以 -1 >>> 1 得到的是
01111111 11111111 11111111 11111111
// 即
2147483647
@AaronConlon
Copy link

很棒,有参考价值,多谢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
js基础 Good for newcomers 重学js 重学js系列 规范+MDN
Projects
None yet
Development

No branches or pull requests

2 participants