1. 程式人生 > >位運算相關概念和應用

位運算相關概念和應用

一 基本概念

機器碼 = 符號位 + 真值

符號位:
正數: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;
......

這段程式碼設計到幾個點:

  1. 將a第k設為0: a = a & ~(1<<k)

  2. 將a第k設為1: a = a | (1<<k)

  3. 取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

應用方面還挺多,後面再考慮加一下…