位運算簡介

位運算基礎:

運算符 用法 描述
按位與( AND) a & b 對於每個比特位,只有兩個操做數相應的比特位都是1時,結果才爲1,不然爲0。
按位或(OR) a | b 對於每個比特位,當兩個操做數相應的比特位至少有一個1時,結果爲1,不然爲0。
按位異或(XOR) a ^ b 對於每個比特位,當兩個操做數相應的比特位有且只有一個1時,結果爲1,不然爲0。
按位非(NOT) ~ a 反轉操做數的比特位,即0變成1,1變成0。
左移(Left shift) a << b a 的二進制形式向左移 b (< 32) 比特位,右邊用0填充。
有符號右移 a >> b 將 a 的二進制表示向右移b(< 32) 位,丟棄被移出的位。
無符號右移 a >>> b 將 a 的二進制表示向右移b(< 32) 位,丟棄被移出的位,並使用 0 在左側填充。

須要注意如下幾點:java

  • 這6種操做符,只有~取反是單目操做符,其它5種都是雙目操做符;
  • 位操做只能用於整形數據,對float和double類型進行位操做會被編譯器報錯;
  • 移位操做都是採起算術移位操做,算術移位是相對於邏輯移位,它們在左移操做中都同樣,低位補0便可,但在右移中邏輯移位的高位補0而算術移位的高位是補符號位。以下面代碼會輸出-4和3;
> -15>>2
-4
> 15>>2
3

由於15=0000 1111(二進制),右移二位,最高位由符號位填充將獲得0000 0011即3。-15 = 1111 0001(二進制),右移二位,最高位由符號位填充將獲得1111 1100即-4。算法

  • 位操做符的運算優先級比較低,由於儘可能使用括號來確保運算順序,不然極可能會獲得莫明其妙的結果。好比要獲得像1,3,5,9這些2^i+1的數字。寫成a = 1 << i + 1是不對的,程序會先執行i + 1,再執行左移操做。應該寫成a = (1 << i) + 1;
  • 另外位操做還有一些複合操做符,如&=、|=、 ^=、<<=、>>=。

原碼、反碼和補碼

1.1 原碼

一個數在計算機中的二進制表示形式,叫作這個數的機器數。機器數是帶符號的,在計算機用一個數的最高位存放符號,正數爲 0, 負數爲 1。因此,爲區別起見,將帶符號位的機器數對應的真正數值稱爲機器數的真值。.net

原碼就是符號位加上真值的絕對值,即用第一位表示符號,其他位表示值。好比 8 位二進制:code

[+1] 原 = 0000 0001
[-1] 原 = 1000 0001

第一位是符號位,由於第一位是符號位,因此 8 位二進制數的取值範圍就是:[1111 1111 , 0111 1111],即:[-127 , 127]htm

1.2 反碼

反碼的表示方法是:正數的反碼是其自己,負數的反碼是在其原碼的基礎上,符號位不變,其他各個位取反。blog

[+1] = [00000001] 原 = [00000001] 反
[-1] = [10000001] 原 = [11111110] 反

1.3 補碼

補碼的表示方法是:正數的補碼就是其自己,負數的補碼是在其原碼的基礎上,符號位不變,其他各位取反,最後 + 1。(即在反碼的基礎上 + 1)ip

[+1] = [00000001] 原 = [00000001] 反 = [00000001] 補
[-1] = [10000001] 原 = [11111110] 反 = [11111111] 補

由於在電腦中存儲數值都是用補碼來進行存儲的,因此對負數的計算首先要先算出它的補碼值內存

左移運算(<<)

value << num

num 指定要移位值;value 移動的位數。get

將左操做數(value)轉爲二進制數後向左邊移動 num 位,而且在低位補 0,高位丟棄。

例如:5 << 2

0000 0000 0000 0000 0000 0000 0000 0101     5 的補碼(同原碼)
0000 0000 0000 0000 0000 0000 0001 0100     左移 2 位後,低位補 0。換算成 10 進製爲 20

若是移動的位數超過了該類型的最大位數,那麼編譯器會對移動的位數取模。如:對 int 類型(最大位數 32)的數值移動 33 位,實際上只移動了 33 % 32 = 1 位。

注:n 位二進制,最高位爲符號位,所以表示的數值範圍:−2(n−1)−2(n−1) —— 2(n−1)−12(n−1)−1,因此模爲:2(n−1)2(n−1)。

在數字沒有溢出的前提下,對於正數和負數,左移一位都至關於乘以 2 的 1 次方,左移 n 位就至關於乘以 2 的 n 次方。如:5 << 2 至關於 5∗22=205∗22=20。

若是移進高階位(int 31 或 long 63 位),那麼該值將變爲負值。如:1 << 31 = -2147483648

右移運算(>>)

value >> num

num 指定要移位值;value 移動的位數。

將左操做數(value)轉爲二進制數後向右邊移動 num 位,符號位不變,高位補上符號位(若左操做數是正數,則高位補 0,若左操做數是負數,則高位補 1),低位丟棄。

右移時,被移走的最高位(最左邊的位)由原來最高位的數字補充,這叫作符號位擴展(保留符號位)(sign extension),在進行右移操做時用來保持負數的符號。

例如:7 >> 2

0000 0000 0000 0000 0000 0000 0000 0111     7 的補碼(同原碼)
0000 0000 0000 0000 0000 0000 0000 0001     右移 2 位後,高位補 0。換算成 10 進製爲 1

例如:-7 >> 2

1000 0000 0000 0000 0000 0000 0000 0111     -7 的原碼
1111 1111 1111 1111 1111 1111 1111 1000     -7 的反碼
1111 1111 1111 1111 1111 1111 1111 1001     -7 的補碼
1111 1111 1111 1111 1111 1111 1111 1110     右移 2 位後,高位補 1
1000 0000 0000 0000 0000 0000 0000 0010     補碼轉原碼。換算成 10 進製爲 -2

正數右移 n 位至關於除以 2 的 n 次方而且捨棄了餘數。如:7 >> 2 至關於: 7/22=17/22=1。

負數右移 n 位至關於除以 2 的 n 次方,若是有餘數 -1。如:-7 >> 2 至關於: 7∗22−1=−27∗22−1=−2。

無符號右移(>>>)

value >>> num

num 指定要移位值;value 移動的位數。

將左操做數(value)轉爲二進制數後向右邊移動 num 位,0 補最高位(忽略了符號位擴展)。

無符號右移運算只是對 32 位和 64 位的值有意義。

例如:-7 >>> 2

1000 0000 0000 0000 0000 0000 0000 0111     -7 的原碼
1111 1111 1111 1111 1111 1111 1111 1001     -7 的補碼
0011 1111 1111 1111 1111 1111 1111 1110     右移 2 位後,高位補 0。換算成 10 進製爲 1073741822

-1的右移:

負數的存儲以補碼(符號位保持不變,其餘位是存儲數的絕對值按位取反加1)方式:故而-1在存儲空間的存放爲:
其值爲2^32-1=4294967255;

11111111  11111111  11111111  11111111

無符號右移(>>>):

故而無符號右移10位:變成以下圖所示:

00000000  00111111  11111111  11111111

其值:

2^22-1=4194303
或
(1<<22)-1=4194303

右移(>>):

11111111  11111111  11111111  11111111

若爲負數,則在存儲時首位表示符號位,其值爲1,表示該值是負數的移位,在移位過程當中,高位補1,

而後求補碼加1;

10000000  00000000  00000000  00000001
-1>>10 結果仍是-1

若符號位是0,表示是正數,在移位過程當中高位補零,二者的前提是符號位保持不變:

其它:

  • Java 中整數類型(byte、short、int 和 long)在內存中是以有符號的二進制補碼錶示。因此位運算時,首先要轉換爲原碼。
  • 補碼轉原碼:補碼轉原碼和原碼轉補碼的方法是同樣的,取反 + 1(補碼的補碼是原碼)。
  • 當位運算數是 byte 和 short 類型時,將自動把這些類型擴大爲 int 型(32 位)。
  • 計算出 n 位二進制數所能表示的最大十進制數位移算法:-1L ^ (-1L << n)~(-1L << 5)
  • byte 和 int 相互轉換
int i = 234;

byte b = (byte) i; // 結果:b = -22
// 轉換過程:
// 0000 0000 0000 0000 0000 0000 1110 1010      # int 234 的補碼(與原碼相等)
//                               1110 1010      # byte 低位截取
//                               1001 0110      # 求得補碼,轉爲 10 進製爲 -22

int x = b ; // 結果爲:x = -22;8 位 byte 的轉 32 的 int,值不變。
int y = b & 0xff; // 結果爲:x = 234; 能夠經過將其和 0xff 進行位與(&)獲得它的無符值
// 轉換過程:
// 1001 0110                                    # byte -22 的原碼
// 1000 0000 0000 0000 0000 0000 0001 0110      # int -22 的原碼
// 1111 1111 1111 1111 1111 1111 1110 1010      # int -22 補碼
// 0000 0000 0000 0000 0000 0000 1111 1111      # 0xff 的二進制數
// 0000 0000 0000 0000 0000 0000 1110 1010      # 和 0xff 進與操做的結果,轉換爲 10 進製爲 234

參考:

位運算簡介及基本技巧

有趣的二進制—高效位運算

位運算——強大得使人懼怕

各類位運算

按位操做符

負數的帶符號和不帶符號的右移運算

補碼與位運算

Java 位運算筆記

相關文章
相關標籤/搜索