Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
代計算機的數據是以二進制的形式進行存儲的。 位運算就是直接對整數在內存中的二進制位進行操作。使用位運算可以提高代碼性能,精簡代碼。
在說位運算前,先簡單說說幾個概念,具體有機器數、真值、原碼、反碼、補碼
一個數在計算機中的二進制表示形式,叫做這個數的機器數。
機器數是帶符號的,計算機中機器數的最高位是符號位, 正數為0, 負數為1。
二進制的位數受機器設備的限制。機器內部設備一次能表示的二進制位數叫機器的字長,一臺機器的字長是固定的。字長8位叫一個字節(Byte),機器字長一般都是字節的整數倍,如字長8位、16位、32位、64位。
例如在8位機器中,+3轉為二進制為 00000011,-3轉為二進制為 10000011,其中00000011和10000011就是機器數。
直接用正號“+”和負號“-”來表示其正負的二進制數叫做機器數的真值。
例如“00000011”和“10000011”是兩個機器數,而它們的真值分別為+0000011(即+3)和-0000011(即-3)。
將數的真值形式中“+”號用“0”表示,“-”號用“1”表示時,叫做數的原碼形式,簡稱原碼。
也可理解為符號位加上真值的絕對值。第一位是符號位,其余位表示數值。
例如:
十進制 | 真值 | 原碼 |
+3 | +0000011 | 00000011 |
-3 | -0000011 | 10000011 |
原碼的表示比較直觀,但是在進行加減法運算時,由于符號位的存在,使得機器的運算比較復雜,所以就引入了反碼和補碼。
對于正數來說,其反碼和原碼完全相同。
對于負數來說,其反碼是在其原碼的基礎上, 符號位不變,其他位取反(0變1,1變0)。
十進制 | 真值 | 原碼 | 反碼 |
+3 | +0000011 | 00000011 | 00000011 |
-3 | -0000011 | 10000011 | 11111100 |
對于正數來說,其補碼和原碼完全相同。
對于負數來說,其補碼是其反碼的末位加1,也就是說在其原碼的基礎上, 符號位不變,其他位取反后加1。
十進制 | 真值 | 原碼 | 反碼 | 補碼 |
+3 | +0000011 | 00000011 | 00000011 | 00000011 |
-3 | -0000011 | 10000011 | 11111100 | 11111101 |
注意:在計算機系統中,數值一律用補碼來表示和存儲。主要原因是為了便于處理和運算。
具體為何需要引入反碼和補碼以及為何計算機系統中數值用補碼來表示和存儲,文章篇幅有限,此塊也不是本文的重點,詳細可以參考這篇文章www.cnblogs.com/zhangziqiu/…
? 在 JavaScript 中,數值是以64位浮點數的形式儲存。但是位運算符只對整數起作用。JavaScript在做位運算時,會事先將其轉換為32位有符號整型(用比特序列表示,即0和1組成)并開始計算,在得到結果后再將其轉回JavaScript的數值類型。
? 因為 js 的整數默認是帶符號數,所以在位運算中,只能使用 31 位,開發者是不能訪問最高位的。在運算時,如果位數超過,那么超過的數字會被丟棄,這會造成各種錯誤的結果,并且沒有任何警告信息。所以,并不是所有計算都適合通過位運算符來計算。
? 位運算符操作的是數值對應的機器數,這個機器數的表示形式就是補碼的形式。(上面說了,為了便于處理和運算,計算機系統中,數值用補碼來表示和存儲。)
? JavaScript中的位運算符有以下:
console.log(5 & 3) // 輸出1
// 5的機器數表示為 00000000 00000000 00000000 00000101
// 3的機器數表示為 00000000 00000000 00000000 00000011
// 所以計算結果為1 00000000 00000000 00000000 00000001
:或運算;兩個運算比較的bit位只要有一個是1,這個bit位就是1
console.log(5 | 3) // 輸出7
// 5的機器數表示為 00000000 00000000 00000000 00000101
// 3的機器數表示為 00000000 00000000 00000000 00000011
// 所以計算結果為7 00000000 00000000 00000000 00000111
^ :異或運算;兩個運算比較的bit位相同則為0,不同則為1
console.log(5 ^ 3) // 輸出6
// 5的機器數表示為 00000000 00000000 00000000 00000101
// 3的機器數表示為 00000000 00000000 00000000 00000011
// 所以計算結果為6 00000000 00000000 00000000 00000110
~:取反運算;0變1,1變0
console.log(~5) // 輸出-6
// 5的機器數表示為 00000000 00000000 00000000 00000101
// 取反之后表示為 11111111 11111111 11111111 11111010
// 該數是負數,負數補碼與原碼不同,先將其轉為原碼的反碼:11111111 11111111 11111111 11111001
// 再轉為原碼:10000000 00000000 00000000 00000110
// 所以結果為:-6
<< :左移;各二進位全部左移若干位,高位丟棄(即左邊超出的丟棄),低位補0(即右邊差的)
console.log(5 << 3) // 輸出40
// 5的機器數表示為 00000000 00000000 00000000 00000101
// 左移三位結果為40 00000000 00000000 00000000 00101000
圖示:
console.log(-1073741822 << 2) // 輸出8
// -1073741822的機器數表示為 11000000 00000000 00000000 00000010
// 左移兩位結果為8 00000000 00000000 00000000 00001000
圖示:
>> :帶符號擴展右移;各二進位全部右移若干位,右邊被移出的丟棄,左邊會復制最左側的位來填充左側(這里最左側的位是符號位,也可以認為對于無符號數,高位補 0,對于有符號數,高位補符號位)。
console.log(5 >> 2) // 輸出1
// 5的機器數表示為 00000000 00000000 00000000 00000101
// 帶符號擴展右移兩位結果為1 00000000 00000000 00000000 00000001
圖示:
console.log(-5 >> 2) // 輸出-2
// -5的機器數表示為: 11111111 11111111 11111111 11111011
// 帶符號擴展右移兩位結果為: 11111111 11111111 11111111 11111110
// 先將該值轉為原碼的反碼: 11111111 11111111 11111111 11111101
// 再轉為原碼: 10000000 00000000 00000000 00000010
// 所以結果為:-2
圖示:
>>> :無符號右移;各二進位全部右移若干位,右邊被移出的丟棄,左邊用0填充。因為符號位變成了 0,所以結果總是非負數。(即便右移 0 個比特,結果也是非負的。)
console.log(5 >>> 2) // 輸出1
// 5的機器數表示為 00000000 00000000 00000000 00000101
// 無符號右移兩位結果為1 00000000 00000000 00000000 00000001
圖示:
console.log(-5 >>> 2) // 輸出1073741822
// -5的機器數表示為: 11111111 11111111 11111111 11111011
// 無符號右移兩位結果為1073741822: 00111111 11111111 11111111 11111110
根據數值的機器數最后一位是0還是1即可判斷,為0就是偶數,為1就是奇數。
function fun(num) {
if ((num & 1)===0) {
return "偶數";
}
if ((num & 1)===1) {
return "奇數";
}
}
console.log(fun(44)); // 輸出偶數
console.log(fun(55)); // 輸出奇數
不使用第三個變量,實現兩個數交換,可以通過以下方式實現
a=a + b;
b=a - b;
a=a - b;
// 或者
a=[b, (b=a)][0];
// 或者
[a, b]=[b, a]
根據異或運算的規則,可以得出一些特性
// a取任何值,下面的結果都成立
a^a=0
a^0=a
// 異或運算且滿足交換律和結合律,即
a^b=b^a
a^b^c=a^(b^c)=(a^c)^b
// 其實與運算也是滿足交換律和結合律的
a&b=b&a
a&b&c=a&(b&c)=(a&c)&b
// 于是就可以推出
a^b^a=b^(a^a)=b^0=b
// 如果 c=a^b 那么 a=c^b,這是因為 a=c^b=a^b^b=a^(b^b)=a^0=a
// 如果 d=a^b^c 那么 a=d^b^c, 以此類推...
那么根據以上的特性,就可以用位運算的方式實現兩數交換,如下
a=a^b
b=a^b
a=a^b
將某個數值帶符號右移一位,相當于該數值除以2;
將某個數值左移一位,相當于該數值除以2;
console.log(6 >> 1) // 輸出3
console.log(6 << 1) // 輸出12
而右移n位,就相當于該數值除以2的n次方
而左移n位,就相當于該數值乘以2的n次方
console.log(32 >> 3) // 輸出4, 相當于32除以2的3次方(8),等于4
console.log(32 << 3) // 輸出256, 相當于32乘以2的3次方(8),等于256
但是用這種用法實現乘除法有很多問題,比如
console.log(7 >> 1) // 輸出3
如果數值操作影響到了符號位,結果會錯誤
console.log(-2147483646 << 1) // 輸出4, 而-2147483646除以2的結果應該是-1073741823
console.log(5.2 | 0) // 輸出5
console.log(-7.2 | 0) // 輸出-7
通過與運算實現:將數值與-1進行或運算從而得到該數值的整數部分
console.log(5.2 & -1) // 輸出5
console.log(-7.2 & -1) // 輸出-7
通過異或運算實現:
console.log(5.2 ^ 0);// 輸出5
console.log(-7.2 ^ 0);// 輸出-7
將數值與另一個數進行兩次異或運算
console.log(5.2 ^ 2 ^ 2); // 輸出5
console.log(-7.2 ^ 2 ^ 2);// 輸出-7
通過取反運算實現:將數值進行兩次取反運算
console.log(~~5.2); // 輸出5
console.log(~~-7.2); // 輸出-7
通過左移或右移運算實現:將數值左移或右移0位
console.log(5.2 >> 0); // 輸出5
console.log(-7.2 << 0); // 輸出-7
位掩碼(BitMask),是”位(Bit)“和”掩碼(Mask)“的組合詞。”位“指代著二進制數據當中的二進制位,而”掩碼“指的是一串用于與目標數據進行按位操作的二進制數字。組合起來,就是”用一串二進制數字(掩碼)去操作另一串二進制數字“的意思。
位掩碼可以用于多種場景,比較常用的,我們可以用它來進行權限管理。
假設我們在做一個直播教學系統,每個學生都有其自己的狀態,比如是否舉手,攝像頭狀態,設備類型三個狀態。
如果我們給每個學生設置三個狀態值,那會很麻煩(這里只是舉例三個,現實場景可能有七八個狀態值)
那么我們可以設計每個學生一個狀態碼,例如這個狀態碼是一個9位的二進制數
我們可以這個數上劃分每個狀態所占的位
例如從最低位(按最低位0)開始
具體可以在代碼中定義一個枚舉:
const enums={
// 舉手狀態
handsUp: {
open: 0b01, // 舉手中
close: 0b10, // 未舉手
unknow: 0b00, // 未知
lowBitPos: 0, // 從第幾位開始使用
bitLength: 2, // 所占位的長度
},
// 攝像頭狀態
camera: {
open: 0b001, // 打開
close: 0b010, // 關閉
disable: 0b011, // 設備故障
ban: 0b100, // 被禁用
unknow: 0b000, // 未知
lowBitPos: 2, // 從第幾位開始使用
bitLength: 3, // 所占位的長度
},
// 設備類型
device: {
window: 0b0001, // window電腦
mac: 0b0010, // 蘋果電腦
ipad: 0b0011, // 蘋果平板
android: 0b0100, // 安卓手機
ios: 0b0111, // 蘋果手機
unknow: 0b0000, // 未知
lowBitPos: 5, // 從第幾位開始使用
bitLength: 4, // 所占位的長度
}
};
例如我隨便舉個例子,假設某個學生的狀態碼為 0b001001001
那么其對應狀態如下
但是在代碼中,我們要如何獲取或修改學生狀態對應的值呢?這就要用到前面所說的位掩碼的知識了
首先是獲取狀態值,我們需要從該狀態碼中截取出指定的從某一位到某一位的值。
// 舉個例子,假設學生此時的狀態碼為0b001001001,獲取該學生的攝像頭狀態
let state=0b001001001
// 因為攝像頭狀態是從低位開始(0開始)第二位到第四位
// 首先將狀態碼右移兩位
let v=state >> enums.camera.lowBitPos // enums上面定義了
// 然后將轉換后的值與0b111進行與運算
let result=v & 0b111
console.log(result) // 輸出結果為3,也就是0b010,如此便獲取到了該學生的攝像頭狀態
上面代碼中,因為攝像頭狀態是占了三個bit位,所以需要與0b111進行與運算來得到結果,但是舉手狀態是兩位,設備類型是四位,就要分別和0b11和0b1111進行與運算,這樣比較麻煩,所以上面的代碼可以優化為
let result=v & ((0b1 << enums.camera.bitLength) - 1)
也可以將所有需要用到的位全是1的值先存在數組里面,方便后續運算
const bitArr=[0b0]
// 但位運算時僅支持 1 << 31大小
for (let i=1; i < 32; i++) {
bitArr.push((0b1 << i) - 1)
}
Object.freeze(bitArr) // 凍結數組 不允許修改
let result=v & bitArr[enums.camera.bitLength]
總結上面代碼,我們可以將獲取狀態值封裝為一個函數
const getProvide=(state, lowBitPos, bitLength)=> {
return (state >> lowBitPos) & bitArr[bitLength]
}
知道如何獲取以后,那么修改該如何呢
例如,目前學生的狀態碼對應的設置狀態為0b0010(蘋果電腦),我們要修改學生的設備狀態為0b0100(安卓手機)
let state=0b001001001
// 首先先獲取設備狀態對應的值
let deviceState=getProvide(state, enums.device.lowBitPos, enums.device.bitLength)
// 然后先清除設備狀態對應的位數區間
let clearState=state - (deviceState << enums.device.lowBitPos)
// 然后重新加上新的狀態區間即可
let result=clearState + (enums.device.android << enums.device.lowBitPos)
console.log(result.toString(2)) // 輸出 010001001
封裝為函數如下
const setProvide=(state, lowBitPos, bitLength, newSingleState)=> {
const get=getProvide(state, lowBitPos, bitLength)
let clearState=state - (get << lowBitPos)
return clearState + (newSingleState << lowBitPos)
}
如此便實現了對學生用戶的權限狀態控制。
者:平臺前端組
轉發鏈接:https://mp.weixin.qq.com/s/MbSTkkzf7nDjtBkmh7Iqbw
者:Dunizb
轉發鏈接:https://mp.weixin.qq.com/s/kxo6tOmaJYdINiIOqls1LA
*請認真填寫需求信息,我們會在24小時內與您取得聯系。