位運算博大精深,本文總結下基本的位運算的概念。
1、整數的二進制碼###
位操作符用於在最基本的層次上,即按內存中表示數值的位來操作數值。ECMAScript中的所有數值都以IEEE-754 64位格式存儲,但位操作符並不直接操作64位的值。而是先將64位的值轉換成32位的整數,然后執行操作,最后再將結果轉換回64位。對於開發人員來說,由於64位存儲格式是透明的,因此整個過程就像是只存在32位的整數一樣。
對於有符號的整數,32位中的前31位用於表示整數的值。第32位用於表示數值的符號:0表示正數,1表示負數。這個表示符號的位叫做符號位。例如,數值18的二進制表示是00000000000000000000000000010010
,或者更簡潔的10010
。
負數同樣以二進制碼存儲,但使用的是二進制補碼(其實正數也是用補碼表示)。計算一個數值的二進制補碼,需要經過下列三個步驟:
- 求這個數值絕對值的二進制碼
- 求二進制反碼,即將0替換為1,將1替換為0
- 得到的二進制反碼加1
比如求-18的二進制碼,首先求得18的二進制碼:
0000 0000 0000 0000 0000 0001 0010
然后求其二進制反碼:
1111 1111 1111 1111 1111 1110 1101
最后,二進制反碼+1
1111 1111 1111 1111 1111 1110 1110
這樣就求得了-18的二進制表示。
在ECMAScript中,當對數值應用位操作符時,后台會發生如下轉換過程:64位的數值被轉換成32位數值,然后執行位操作,最后再將32位的結果轉換回64位數值。這樣,表面上看起來就好像是在操作32位數值。但這個轉換過程也導致了一個嚴重的負效應,即在對特殊的NaN和Infinity值應用位操作時,這兩個值都會被當成0來處理。如果對非數值進行位操作,會先使用Number()函數將該數值轉換成一個數值(自動完成),然后再應用位操作,得到的結果是一個數值。
2、~ & | ^
接下來介紹4個位操作符。
按位非操作符由一個波浪線(~)表示,執行按位非的結果就是返回數值的反碼。
var num = 25;
console.log(~num); // -26
對25執行按位非操作,結果得到了-26,這也驗證了之前說的結論,“一個負數的二進制碼是該數絕對值的反碼+1”
按位與操作符由一個和號字符(&)表示,它有兩個操作符數,按位與操作只在兩個數值的對應位都是1時才返回1,否則0。
按位或操作符由一個豎線符號(|)表示,同樣也有兩個操作符,在有一位是1的情況下返回1,否則0.
按位異或操作符由一個插入符號(^)表示,在兩個數值對應位上只有一個1時返回1,否則0。
3、<< >> >>>
左移符號由兩個小於號(<<)表示,這個操作符會將數值的所有位向左移動指定的位數。在左移后,原數值右側空出的位由0填補。
左移一位其實就相當於將原數值乘以2,左移不會影響操作數的符號位。
右移操作符由兩個大於號(>>)表示,這個操作符會將數值向右移動,但保留符號位。在移位過程中,空缺位出現在原數值的左側,符號位的右側,用符號位的值來填充空位。右移一位相當於原數除2后向下取整。
無符號右移操作符由三個大於號(>>>)表示,這個操作符會將數值的所有32位都向右移動。對正數來說,無符號右移的結果和有符號相同,但是對負數來說就不一樣了,無符號右移會把負數的符號位也進行移動,左邊空出位置用0填充。
var num = -64;
console.log(num >> 5); // -2
console.log(num >>> 5); // 134217726
對-64進行無符號右移操作,將其用二進制碼表示:
1111 1111 1111 1111 1111 1111 1100 0000
右移5位后:
0000 0111 1111 1111 1111 1111 1111 1110
即十進制的134217726。
notice:並沒有無符號左移!
4、關於位運算的坑###
了解的基本的位運算操作后,這里我要談談我經常碰到的一個坑。
int32的取值范圍是-2^31 ~ 2^31-1,於是我經常會去求1<<31的值,但是是溢出的...
console.log(1 << 31); // -2147483648
原因很簡單,1的左邊只有30位可以移動,實際上把1移到了符號位上,得到了:
1000 0000 0000 0000 0000 0000 0000 0000
所以要表示int32的區間范圍,可以這樣:
console.log(1 << 31); // -2147483648
console.log(1 << -1); // -2147483648
console.log(-0x80000000); // -2147483648
console.log(~(1 << 31)); // 2147483647
console.log(-(1 << -1) - 1); // 2147483647
console.log(0x7fffffff); // 2147483647
console.log(Math.pow(2, 31) - 1); // 2147483647
console.log((1 << 30) * 2 - 1); // 2147483647