位操作

位操作是程式設計中對位陣列或二進位制數的一元和二元操作。在許多古老的微處理器上，位運算比加減運算略快，通常位運算比乘除法運算要快很多。在現代架構中，位運算的運算速度通常與加法運算相同（仍然快於乘法運算），但是通常功耗較小，因為資源使用減少。

位運算子

下面的解釋中，任何二進位制位的表示都從右側（最低位）開始計數，向左進。舉個例子，二進位制值 0001（十進位制 1）除第一位（即最右邊）每位上都是 0。

1.1 取反（NOT）

取反是一元運算子，對一個二進位制數的每一位執行邏輯反操作。使數字 1 成為 0，0 成為 1。例如：

NOT 0111（十進位 7）

= 1000（十進位 8）

NOT 10101011 (十進位制 171)

= 01010100 (十進位制 84)

結果等於該值的補碼減一。如果使用補碼算術，則 NOT x = -x − 1。

對於無符號整數，數的按位補碼是其在無符號整數範圍的中點另一邊的 “映象”。例如，對於 8 位無符號整數，NOT x = 255 - x，可以在圖上將其視覺化為一條向下的線，相當於把從 0 到 255 遞增的範圍，“翻轉” 到從 255 到 0 遞減的範圍。一個簡單而有說明性的使用例子是反轉灰度影像，其中每個畫素儲存為無符號整數。

許多程式設計語言（包括 C 語言家族），取反運算子用波浪線 "~" 表示。值得注意的是此運算子與 “邏輯非（!）” 運算子不同。在 C++ 中，邏輯非將數字整體看做一個布林型別 —— 將真值轉化為假，將假值轉化為真；而 C 語言將 0 轉化為 1，將非零值轉化為 0。“邏輯非” 並不是一個位操作。

1.2 按位或（OR）

按位或處理兩個長度相同的二進位制數，兩個相應的二進位中只要有一個為 1，該位的結果值就為 1。例如

0101（十進位制 5）

OR 0011（十進位制 3）

= 0111（十進位制 7）

在 C 類程式設計語言中，按位或運算子是 "|"。這一運算子需要與邏輯或運算子（

）區別開來。

按位或能夠將每一位看做旗標；在二進位制數中的每一位可以表示不同的布林變數。應用按位或操作可以將二進位制數的某一位設為 1。例如

0010（十進位制 2）

能夠看做包含 4 個旗標的組合。第 1，2，4 旗標為 0；第 3 個旗標為 1。利用按位或可以將第 1 個旗標設定為 1，而其他旗標不變。

0010（十進位制 2）

OR 1000（十進位制 8）

= 1010（十進位制 10）

這一技巧通常用來儲存程式中的大量布林變數。

1.3 按位異或（XOR）

按位異或運算，對等長二進位制模式或二進位制數的每一位執行邏輯異或操作。操作的結果是如果某位不同則該位為 1，否則該位為 0。例如

0101

XOR 0011

= 0110

在類 C 語言中，按位異或運算子是 "^"。

組合語言的程式設計師們有時使用按位異或運算作為將暫存器的值設為 0 的捷徑。用值的自身對其執行按位異或運算將得到 0。並且在許多架構中，與直接載入 0 值並將它儲存到暫存器相比，按位異或運算需要較少的中央處理單元時鐘週期。

按位異或也可以用於在位元集合中切換旗標。給出一個位元模式，

0010

第一和第三位能夠透過按位異或運算使用同時切換。

0010

XOR 1010

= 1000

這一技巧可用於操作表示布林變數的位元模式。

1.4 按位與（AND）

按位與處理兩個長度相同的二進位制數，兩個相應的二進位都為 1，該位的結果值才為 1，否則為 0。例如：

0101

AND 0011

= 0001

此操作可以被用來檢查一個特定的位是 1 還是 0。例如，給定一個二進位制模式 0011（十進位制 3），我們用按位與和一個僅在第二位為 1 的二進位制模式來確定第二位是否為 1：

0011 (十進位制 3)

AND 0010 (十進位制 2)

= 0010 (十進位制 2)

因為結果 0010 是非零的，所以我們知道原模式中的第二位是 1。這通常被稱為位掩碼。（類似的，使用紙膠帶覆蓋不應更改的部分或不感興趣的部分。在這種情況下，0 值會遮蔽不感興趣的位。）

按位與可用於清除暫存器的選定位（或旗標），其中每個位代表一個單獨的布林狀態。 這種技術是一種使用盡可能少的記憶體來儲存大量布林值的有效方法。

例如，0110（十進位制 6）可以被認為是一組四個旗標，其中第一個和第四個旗標是清除 (0)，第二和第三個旗標是設定 (1)。 第三個旗標可以透過按位與僅在第三位具有零的模式來清除：

0110 (十進位制 6)

AND 1011 (十進位制 11)

= 0010 (十進位制 2)

因為這條性質，透過查詢最低位的值檢查一個二進位制數的奇偶性變得容易。用以上的例子：

0110 (十進位制 6)

AND 0001 (十進位制 1)

= 0000 (十進位制 0)

因為 6 按位與 1 是 0，6 可以被 2 整除，所以 6 是偶數。

在類 C 語言中，按位與用 '&' 表示。

1.5 數學等價物

假設，對於非負整數，按位運算可以被寫成如下形式：

1.6 所有二元邏輯運算子的真值表

兩個有 16 種可能的真值函式；這定義了一個真值表。

這是兩位 P 和 Q 的按位等效運算：

移位

移位是一個二元運算子，用來將一個二進位制數中的每一位全部都向一個方向移動指定位，溢位的部分將被捨棄，而空缺的部分填入一定的值。在類 C 語言中，左移使用兩個小於符號 ">" 表示。

2.1 位定址

如果暫存器的寬度（通常為 32 甚至 64）大於最小可定址單元（通常稱為位元組）的位數（通常為 8），則移位操作會促使從位元組到位的定址策略。因此，進位制的標準數字書寫中，取 “左” 和 “右” 方向，使得左移增加數字的值，右移減少數字的值 —— 如果先讀取左側數位，這就是大端位元組序。忽略暫存器兩端的邊界效應，算術和邏輯移位操作的行為相同，移動 8 位將位模式轉移 1 個位元組位置，方式如下：

小端序：左移 8 個位置，位元組地址加 1；右移 8 個位置，位元組地址減 1。
大端序：左移 8 個位置，位元組地址減 1；右移 8 個位置，位元組地址加 1。

2.2 算術移位

在算術移位中，溢位兩端的位都被丟棄。算術左移中，右側補上 0；算術右移中，左側補上（補碼中的最高位），以保持原數的符號不變。

這個例子使用一個 8 位暫存器，解釋為補碼：

00010111 (十進位制 +23) LEFT-SHIFT

= 00101110 (十進位制 +46)

10010111 (十進位制 −105) RIGHT-SHIFT

= 11001011 (十進位制 −53)

在第一種情況下，最左邊的數字被移到暫存器的末尾，新的 0 被移到最右邊的位置。 在第二種情況下，最右邊的 1 被移出（可能進入了），一個新的 1 被複制到最左邊的位置，保留了數字的符號。 多個移位有時會縮短為一個移位，減少了幾位。 例如：

00010111 (decimal +23) LEFT-SHIFT-BY-TWO

= 01011100 (decimal +92)

算術左移 n 位等價與乘以 2n (前提值沒有整數溢位）。一個補碼的值算術右移 n 位等價與除以 2n 下取整。如果二進位制數被視為一的補碼，則相同的右移運算會導致除以 2n 和向零舍入。

2.3 邏輯移位

應用邏輯移位時，移位後空缺的部分全部填 0。因此，邏輯左移和算術左移完全相同。

但是，由於邏輯右移將值 0 位插入最高位，而不是複製符號位，因此它適用於無符號二進位制數，而算術右移適用於有符號補碼二進位制數。

0001（十進位制 1）

> 2（右移 2 位）

= 0010（十進位制 2）

2.4 迴圈移位

另一種移位是迴圈移位。

2.4.1 旋轉

在此操作中，有時稱為迴圈無進位，位被 “旋轉”，就好像暫存器的左端和右端連線在一起一樣。 在左移期間移入右側的值是從左側移出的任何值，右移操作時反之亦然。 如果需要保留所有現有位，這很有用，並且它經常用於數字密碼學中。

2.4.2 進位旋轉

進位旋轉是一種旋轉操作的變種，其中移入（在任一端）的位是進位標誌的舊值，移出的位（在另一端）成為進位標誌的新值。

一個簡單的進位旋轉可以模擬邏輯和算術移位，只需提前設定好進位標誌。比如，如果進位標誌是 0，那麼 x RIGHT-ROTATE-THROUGH-CARRY-BY-ONE 是邏輯右移一位；如果進位標誌裡是符號位的複製，那麼 x RIGHT-ROTATE-THROUGH-CARRY-BY-ONE 是算術右移一位。因為這些原因，一些微控制器像低端 PIC 微控制器只有旋轉和進位旋轉，並不擔心算術或邏輯移位。

當對大於處理器的本機字長的數字執行移位時，進位旋轉特別有用，因為如果一個大數儲存在兩個暫存器中，從第一個暫存器的一端移出的位必須在另一端進入第二個暫存器。使用迴圈進位時，該位在第一次移位期間 “儲存” 在進位標誌中，準備在第二次移位期間移入而無需任何額外準備。

2.5 在高階語言中

2.5.1 類 C 語言和 Python

在類 C 語言和 Python 中，邏輯移位運算子是左移 “” 和右移 “>>”。移位的位置數作為運算子的第二個引數給出。例如，

x = y 將 x 賦值為 y 左移兩位的結果，其等價於乘以四。

移位可能導致實現定義的行為或未定義行為，因此在使用它們時必須小心。 在 C 和 C++ 中，移位大於或等於字大小的位數是未定義的行為。右移負值是實現定義的，但良好的編碼實踐不建議這樣做；如果結果無法在結果型別中表示，則左移有符號值的結果是未定義的。

在 C# 中，當第一個運算元是整形或長整形時，右移是算術移位。 如果第一個運算元是無符號整形或無符號長整形，則右移是邏輯移位。

2.5.2 Java

JAVA 中有一個特有的無符號右移運算子 “>>>”。此操作將忽略運算元的符號，同樣的還有 “>>>=”。

2.5.3 JavaScript

JavaScript 使用按位運算將兩個或多個數字中的每一個求值為 1 或 0。

2.5.4 Pascal

在 Pascal 及其所有方言中（如 Object Pascal 和 ），邏輯左移運算子和右移運算子分別是 “shl” 和 “shr”。 即使對於有符號整數， shr 的行為也類似於邏輯移位，並且不會複製符號位。 要移動的位置數作為第二個引數給出。 例如，下面將 y 左移兩位的結果賦值給 x：

x := y shl 2;

應用

位運算是必要的，尤其是在裝置驅動程式、低階圖形、通訊協議包組裝和解碼等低階程式設計中。

儘管機器通常具有執行算術和邏輯運算的有效內建指令，但所有這些運算都可以透過以各種方式組合按位運算子和零測試來執行。例如，這裡是的虛擬碼實現，展示瞭如何僅使用位移和加法將兩個任意整數 a 和 b（a 大於 b）相乘：

c ← 0

while b ≠ 0

if (b and 1) ≠ 0

c ← c + a

left shift a by 1

right shift b by 1

return c

另一個例子是加法的虛擬碼實現，展示瞭如何使用按位運算子和零測試計算兩個整數 a 和 b 的和：

while a ≠ 0

c ← b and a

b ← b xor a

left shift c by 1

a ← c

return b