位運算相關概念和應用
阿新 • • 發佈:2018-12-21
一 基本概念
機器碼 = 符號位 + 真值
符號位:
正數:0
負數:1
1 原碼
原碼 = 符號位 + 真值
比如:
- +1: 0 001
- -1: 1 001
2 反碼
- 正數反碼 = 原碼
- 負數反碼 = 符號位 + 真值取反
比如:
- +1: 0 001
- -1: 1 110
3 補碼
- 正數反碼 = 原碼
- 負數反碼 = 反碼 + 1
比如:
- +1: 0 001
- -1: 1 111
-1的補碼計算過程:
-1 = 1 110 + 1 = 1 111
二 常用位運算
1 按位與
操作位同為1,則為1,否則為0
比如:
1 & 2 = 0001 & 0010 = 0000 = 0 1 & -1 = 0001 & 1111 = 0001 = 1
2 按位或
操作位同為0,則為0,否則為1
比如:
1 | 2 = 0001 | 0010 = 0011 = 3
1 | -1 = 0001 | 1111 = 1111 = -1
3 按位異或
操作位不同則為1,否則為0
比如:
1 ^ 2 = 0001 ^ 0010 = 0011 = 3
1 ^ -1 = 0001 ^ 1111 = 1110 = -2
4 按位取反
操作位0變1,1變0
比如:
~1 = ~0001 = ~1110 = -2
~2 = ~0010 = 1101 = -3
~-1 = ~1111 = 0000 = 0
5 左移
右邊補0,去掉多餘的高位
比如:
1 << 1 = 0001 << 1 = 00010 = 2 2 << 2 = 0010 << 2= 001000 = 8 -1 << 3 = 1111 << 3= 1111000 = -8
6 右移
左邊補符號位,去掉多餘的低位
比如:
1 >> 1 = 0001 >> 1 = 0000 = 0
2 >> 1 = 0010 >> 1= 0001 = 1
4 >> 1 = 0100 >> 1= 0010 = 2
4 >> 2 = 0100 >> 2= 0001 = 1
-1 >> 1 = 1111 >> 1 = 1111 = -1
7 無符號右移
左邊補0,去掉多餘的低位
比如:
1 >>> 1 = 0001 >> 1 = 0000 = 0 2 >>> 1 = 0010 >> 1= 0001 = 1 4 >>> 1 = 0100 >> 1= 0010 = 2 4 >>> 2 = 0100 >> 2= 0001 = 1 -1 >>> 1 = (1...111) >> 1 = (0...111) = 最大正整數
三 位運算應用
1 判斷奇偶
- 偶數:a & 1 == 0
- 奇數:a & 1 == 1
比如:
1 & 1 = 0001 & 0001 = 0001 = 1
2 & 1 = 0010 & 0001 = 0000 = 0
3 & 1 = 0011 & 0001 = 0001 = 1
4 & 1 = 0100 & 0001 = 0000 = 0
2 運算
- 乘法:a * (2^n) = a << n
- 除法:a / (2^n) = a >> n
- 取模: a % (2^n) = a & (2^n - 1)
比如:
3 * 4 = 3 * (2 ^ 2) = 3 << 2 = 0011 << 2 = 1100 = 12
12 / 4 = 12 / (2 ^ 2) = 1100 >> 2 = 0011 = 3
3 % 4 = 3 & (2^2 - 1) = 3 & 3 = 0011 & 0011 = 0011 = 3
11 % 8 = 11 & (2^3 - 1) = 11 & 7 = 1011 & 0111 = 0011 = 3
3 記錄多個狀態
假如,有一個需求,要記錄角色頭頂的公告板要顯示哪些內容,比如有名字,等級,稱號,工會三個,通常我們需要三個變數,分別記錄每個是否顯示,使用位運算可以精簡如下:
// 定義
let showFlag = 0;
const let NAME_POS = 1 << 0; // 0001 第一位表示名字
const let LV_POS = 1 << 1; // 0010 第二位表示等級
const let TITLE_POS = 1 << 2; // 0100 第三位表示稱號
const let GUILD_POS = 1 << 3; // 1000 第四位表示工會
let isShowName, isShowLv, isShowTitle, isShowGuild;
// 名字操作
showFlag = showFlag | NAME_POS; // 0001 顯示名字
isShowName = showFlag & NAME_POS = 0001 & 0001 = 0001 = 1;
showFlag = showFlag & ~NAME_POS; // 0000 隱藏名字
isShowName = showFlag & NAME_POS = 0000 & 0001 = 0000 = 0;
// 等級操作
showFlag = showFlag | LV_POS; // 0010 顯示等級
isShowLv = showFlag & LV_POS = 0010 & 00010 = 0010 = 2;
showFlag = showFlag & ~LV_POS; // 0000 隱藏等級
isShowLv = showFlag & LV_POS = 0000 & 0010 = 0000 = 0;
// 顯示名字和稱號
showFlag = showFlag | NAME_POS; // 0001 顯示名字
showFlag = showFlag | TITLE_POS; // 0101 顯示等級
isShowName = showFlag & NAME_POS = 0101 & 0001 = 0001 = 1;
isShowTitle = showFlag & TITLE_POS = 0101 & 0100 = 0100 = 4;
......
這段程式碼設計到幾個點:
-
將a第k設為0: a = a & ~(1<<k)
-
將a第k設為1: a = a | (1<<k)
-
取a第k位的值: (a>>k) & 1
例如:
let a = 10;// 1010
// 第3位設為0
a = a & (1 << 3) = 1010 & ~(0001 << 3) = 1010 & ~1000 = 1010 & 0111 = 0010 = 2;
// 取3位的值
(a >> 3) & 1 = (0010 >> 3) & 0001 = 0000 & 0001 = 0
// 第2位設為1
a = a | (1 << 2) = 0010 | (0001 << 2) = 0010 | 0100 = 0110 = 6;
// 取2位的值
(a >> 2) & 1 = (0110 >> 2) & 0001 = 0001 & 0001 = 1
應用方面還挺多,後面再考慮加一下…