JavaScript 运算子大概可以分为几大类:算数运算子、逻辑运算子、赋值运算子、比较运算子和位元运算子...
大部分的运算子在阅读规范後都能快速上手,但其中的位元运算子在理解上相对没那麽直觉,但 只要掌握了二进制表达方式,这就只是块小小小鸡蛋糕了
所以 先从了解 二进制binary 开始吧!
在数字系统里,以进位制表达的方式有 2、8、10、16 进位等,而一般日常熟悉的数字都是十进制,也就是以 10 作为进位基数的表达方式。
二进制 就是以 2 为底数的记数方式,以1
和0
做数字表示,目前计算机就是以二进制的方式来储存资料,1
代表open 0
代表 off, 而每个数字就称为一个 位元bit。
下图是二进制与十进制数字的表示比较
这次要介绍 6 种位元运算子 bitwise operator :
& Bitwise AND
| Bitwise OR
~ Bitwise NOT
^ Bitwise XOR
<< Left Shift
>> Sign-Propagating Right Shift
&
位元运算子是不是很眼熟? 这个符号 &
在逻辑运算子中也有!
逻辑运算子中的 And &&
代表两个条件都符合才是 true
, 其一不符合就是 false
true && true === true
false && false === false
false && true === false
而位元运算子的 &
也是同样逻辑, 先将数字转换为二进制後,把握 0
代表 false, 1
代表 true 的原则,再来就是一样的逻辑判断。
所以当 1 和 0 比对,等同比较 true && false
,结果是 false 0
那 1 和 1 呢? true && true
比对的结果当然就是 true 1
小试身手 >> 3 & 2
结果是多少?
//先将 3 和 2 转为二进制表达
//每个位元做 1 , 0 比较
011 -> 3
& 010 -> 2
--------
010 -> 2
第一位: 1 & 0 === 0
第二位: 1 & 1 === 1
第三位: 0 & 0 === 0
再来一题 >> 5 & 7
?
101 -> 5
& 111 -> 7
--------
101 -> 5
由上面的举例发现, 在二进制表示法中,奇数的第一位数都是 1
,偶数的第一位都是 0
,利用此特性就可以轻松筛选出奇数偶数
//重点比对第一位, 所以配上 & 1 判断第一位是否为 1 或 0
10 & 1 ->0000
9 & 1 -> 0001
8 & 1 -> 0000
7 & 1 -> 001
6 & 1 -> 000
5 & 1 -> 001
4 & 1 -> 000
3 & 1 -> 001
2 & 1 -> 000
1 & 1 -> 001
//奇数 & 1 结果都是 1
//偶数 & 1 结果都是 0
//实作判断奇数偶数 function
function isOdd(number){
return (number & 1)===1;
}
function isEven(number){
return (number & 1)===0;
}
isOdd(3) === true
isEven(6) === true
|
位元运算子|
运算子只是将比对规则改为 OR 判断,有 1
就 return 1
否则 return 0
1 true || 1 true === 1 true
0 false || 0 false === 0 false
0 false || 1 true === 1 true
刚刚 3 & 2 === 2
改为 3 | 2
?
011 -> 3
| 010 -> 2
------
011 -> 3
第一位: 1 | 0 === 1
第二位: 1 | 1 === 1
第三位: 0 | 0 === 0
&
运算子: 1 & 1
return 1
, 而0 & 1
return 0
|
运算子: 1 | 1
return 1
, 而0 | 1
return 1
^
位元运算子^
XOR 运算子,全名 Exclusive Or,与一般的逻辑不同,^
是当数字两两相同是 false 0
,不同则是true 1
一样用 3 ^ 2
试看看吧
011 -> 3
^ 010 -> 2
------
001 -> 1
第一位: 1 ^ 0 === 1
第二位: 1 ^ 1 === 0
第三位: 0 ^ 0 === 0
<<
Left Shift Operator 和 >>
Right Shift Operator这两个运算子用来移动数字往符号指向的方向,<<
代表位元向左移, 空出来的位元补上0
,>>
代表位元向右移。
~
位元运算子~
NOT 运算子使用的是 补数 implement 的概念,数字被转换为 32 位元的二进制表达,将 0
转为1
, 1
转为 0
看到这个名词时 google 看了很多文章,wiki 的解释点了不下 10 次
正数和 0 的二补数就是该数字本身,负数的二补数则是将其对应正数按位元取反再加 1 - wikipedia
一直无法透彻理解为什麽要这样设计,直到我看到这个 youtube 影片
Twos complement: Negative numbers in binary 推推! 原来是这样啊
实际上电脑会分配一个符号位 sign bit,来作为正负号表示, 0
表示正数 1
表示负数
第一种最直觉的负数表示为 原码 sign-and-magnitude
但使用这种表达方式会发现一个问题
电脑进行 -5 + 5
的运算时,结果会是 1101 + 0101 = 10010 等於 2
?
安堆~~~~
//原码 sign-and-magnitude
sign
bit 4 2 1
--------------
1 1 1 1 // -7
1 1 1 0 // -6
1 1 0 1 // -5
1 1 0 0 // -4
1 0 1 1 // -3
1 0 1 0 // -2
1 0 0 1 // -1
1 0 0 0 // -0
0 0 0 0 // 0
0 0 0 1 // 1
0 0 1 0 // 2
0 0 1 1 // 3
0 1 0 0 // 4
0 1 0 1 // 5
0 1 1 0 // 6
0 1 1 1 // 7
看来上面那种负数表示方法不太适用於电脑运算
那有另一种表示方法称为 一补数(ones' complement)
在一补数表示时要做位元转换,负数是将 1
0
互换,正数不变动。
所以 -5
是从 0101
位元转换为 1010
表示
那这种方法有解决加总的结果吗?
再试试 -5 + 5
, 1010 + 0101 = 10000 等於 -0
恩 看来好一点,但这个负号不觉得怪怪的吗 -0
?
再另外试试 5 + (-3)
0101 + 1100 = 100001 因为只取4位数先不看最左边的 1,加总起来的数字变为 1
不行啊 数字还是不对
//一补数 ones' complement
sign
bit 4 2 1
--------------
1 0 0 0 // -7
1 0 0 1 // -6
1 0 1 0 // -5
1 0 1 1 // -4
1 1 0 0 // -3
1 1 0 1 // -2
1 1 1 0 // -1
1 0 0 0 // -0
0 0 0 0 // 0
0 0 0 1 // 1
0 0 1 0 // 2
0 0 1 1 // 3
0 1 0 0 // 4
0 1 0 1 // 5
0 1 1 0 // 6
0 1 1 1 // 7
在一补数的正负数加总中可以发现和正确的答案都只差 1
所以 就产生了 一补数加上 1 的方式 二补数(two's complement)
再试一次刚刚的加总 -5 + 5
1011 + 0101 = 10000 一样不看最左边的数字,很好 等於0
那 5 + (-3)
呢?
0101 + 1101 = 10010
耶 是我们要的 2
//二补数 two's complement
sign
bit 4 2 1
--------------
1 0 0 1 // -7
1 0 1 0 // -6
1 0 1 1 // -5
1 1 0 0 // -4
1 1 0 1 // -3
1 1 1 0 // -2
1 1 1 1 // -1
0 0 0 0 // 0
0 0 0 1 // 1
0 0 1 0 // 2
0 0 1 1 // 3
0 1 0 0 // 4
0 1 0 1 // 5
0 1 1 0 // 6
0 1 1 1 // 7
其实二补数的负数算法也很简单,只要将最左边的数字带上负号做加总就跟 原码 sign-and-magnitude 算法一样
1001 -> -8+1 = -7
1010 -> -8+2 = -6
二补数很好的解决了加总的问题,也不存在奇怪的 -0
现在再看看 wiki 说的
正数和 0 的二补数就是该数字本身。负数的二补数则是将其对应正数按位元取反再加 1
是不是就理解了呢?
看完上面了解补数的运作後,~
NOT 就是对每个位元进行补数转换
sign
bit 4 2 1 ~ NOT
---------------------------------------
1 0 0 1 // -7 | ~-7 0110 // 6
1 0 1 0 // -6 | ~-6 0101 // 5
1 0 1 1 // -5 | ~-5 0100 // 4
1 1 0 0 // -4 | ~-4 0011 // 3
1 1 0 1 // -3 | ~-3 0010 // 2
1 1 1 0 // -2 | ~-2 0001 // 1
1 1 1 1 // -1 | ~-1 0000 // 0
0 0 0 0 // 0 | ~0 1111 // -1
0 0 0 1 // 1 | ~1 1110 // -2
0 0 1 0 // 2 | ~2 1101 // -3
0 0 1 1 // 3 | ~3 1100 // -4
0 1 0 0 // 4 | ~4 1011 // -5
0 1 0 1 // 5 | ~5 1010 // -6
0 1 1 0 // 6 | ~6 1001 // -7
0 1 1 1 // 7 | ~7 1000 // -8
如果要在脑中做补位转来转去太困难,其实也可以直接套用这个公式 -(x + 1)
,就可以知道 ~
下的数字是多少了!
做做下面几题练练看吧
~16
~56
~63
sign
bit
64 32 16 8 4 2 1
------------------------------------
0 0 1 0 0 0 0 // 16
1 1 0 1 1 1 1 // ~16 -> -64+32+8+4+2+1 = -17
0 1 1 1 0 0 0 // 56
1 0 0 0 1 1 1 // ~56 -> -54+4+2+1 = -57
0 1 1 1 1 1 1 // 63
1 0 0 0 0 0 0 // ~63 -> -64
~~
这里教个小技巧,你知道在JS中 ~~ 3.2
是什麽意思吗
刚刚学到 ~
是补位,那两个 ~~
是补位再补位,等於自己本身,那 这个有什麽意义吗?
适用在对非整数数字无条件舍去! 因为 JavaScript 位元运算子中都是将数字转为 32 位元的整数呈现,所以可以利用这个原则得到取整数这个效果
//对小数点 2.5 进行两次 NOT 位元运算
~2.5 === -3
~(-3)=== 2
以上
一直很想好好整理的位元运算子完成了呢
其中补数观念还不太理解的朋友真的推荐去看看这支影片 Twos complement: Negative numbers in binary
对了 在 Mac 的计算机也可以进行位元运算唷~~~
在计算机上按快捷键 cmd + 3
就能快速切换成程序设计计算机
来玩玩看吧!
<<: LiteX/VexRiscv 简介与使用 (一) 太初有光
>>: Graph-Tree: uva 615 Is It A Tree?
Abstract 卡哇勾嘎!!!想必大家都有开发过Publisher-Subscriber架构,有些...
目的 将程序的组成转换成有上下阶级的结构(或称:树状结构),方便使用者不论从哪个节点、叶子使用,都可...
掌握人才状况 以表现与潜能为人才定位 我们审慎面试人进来後,对於如何有效沟通与解决问题的mental...
大家好我是长风青云。今天是铁人赛21天。我们算是半只脚踏入演算法的阶段。(因为Sort的部分DS和A...
接下来要实作跳转之後的activty连接着viewpager跟tabitem去对应到相对的fragm...