數(shù)據(jù)在計算機(jī)中都是以補(bǔ)碼形式存放的,位運(yùn)算也是以一個數(shù)的補(bǔ)碼進(jìn)行運(yùn)算,結(jié)果也自然也是一個補(bǔ)碼,這點(diǎn)在分析計算過程時非常重要。
編碼
原碼
原碼就是真正存儲的數(shù)值,比如存?zhèn)€ 7,那么它的所有進(jìn)制表示形式都應(yīng)該為 7,一個數(shù)的原碼也就做這個數(shù)的真值。例:
[+7] = [0000 0111]原碼或真值 = [0000 0111]補(bǔ)碼或機(jī)器數(shù)
[-7] = [1000 0111]原碼或真值 = [1111 1001]補(bǔ)碼或機(jī)器數(shù)
反碼
正數(shù)的反碼是其本身,負(fù)數(shù)的反碼是在其原碼基礎(chǔ)上,符號位不變,其余各個取反。對于反碼表示的是負(fù)數(shù),人腦是無法直觀的看出來它的數(shù)值,通常要將其轉(zhuǎn)換成原碼。例:
[+1] = [0000 0001]原 = [0000 0001]反
[-1] = [1000 0001]原 = [1111 1110]反
補(bǔ)碼
正數(shù)的補(bǔ)碼就是其本身,負(fù)數(shù)的補(bǔ)碼是在其原碼的基礎(chǔ)上,符號位不變,其余各位取反,最后加1(即反碼加1)。一個數(shù)的補(bǔ)碼也叫這個數(shù)的機(jī)器數(shù)。對于補(bǔ)碼表示的是負(fù)數(shù),人腦也是無法直觀的看出來它的數(shù)值,通常要將其轉(zhuǎn)換成原碼。例:
[+1] = [0000 0001]原碼或真值 = [0000 0001]反 = [0000 0001]補(bǔ)碼或機(jī)器數(shù)
[-1] = [1000 0001]原碼或真值 = [1111 1110]反 = [1111 1111]補(bǔ)碼或機(jī)器數(shù)
有符號數(shù)與無符號數(shù)
一個數(shù)有正負(fù)之分,計算機(jī)里則為有符號數(shù)與無符號數(shù)。人腦在計算中用 " + " 號表示正數(shù),用 " - " 減號表示負(fù)數(shù),但計算機(jī)不能把 " + " 、" - " 號顯示出來。計算機(jī)中用二進(jìn)制位最高位表示正負(fù)(0正1負(fù))。以8位 int 型為例:
// 無符號 int
[00000000](最小 0)
[11111111](最大 2^7 = 127)
// 有符號 int
[01111111](正數(shù)最大 2^7=127)
[11111111](負(fù)數(shù)最小 -(2^7 + 1) = -128)
注意:之所以負(fù)數(shù)會比正數(shù)多表示一個數(shù),是因為正0(00000000),負(fù)0(10000000)都表示0,所以前輩們將負(fù)0這個狀態(tài)表示 -128
計算機(jī)計算兩個數(shù)的加減過程
對于數(shù)與數(shù)之間的運(yùn)算,人腦可以知道第一位是符號位, 在計算的時候我們會根據(jù)符號位, 選擇對真值區(qū)域的加減。 但是對于計算機(jī), 加減乘數(shù)已經(jīng)是最基礎(chǔ)的運(yùn)算, 要設(shè)計的盡量簡單. 計算機(jī)辨別「符號位」顯然會讓計算機(jī)的基礎(chǔ)電路設(shè)計變得十分復(fù)雜! 于是人們想出了將符號位也參與運(yùn)算的方法. 我們知道,根據(jù)運(yùn)算法則減去一個正數(shù)等于加上一個負(fù)數(shù), 即: 1-1 = 1 + (-1) = 0 , 所以機(jī)器可以只有加法而沒有減法, 這樣計算機(jī)運(yùn)算的設(shè)計就更簡單了。
分別用原碼、反碼、補(bǔ)碼計算 1 - 1:
// 用原碼運(yùn)算
// 結(jié)果顯然不對
[0000 0001]原 + [1000 0001]原 = [1000 0010]原 = -2
// 用反碼運(yùn)算
// 結(jié)果部分正確,雖然人類看起來 +0 和 -0 是一樣的,但是 0 帶符號是沒有意義的,
// 而且會有 [0000 0000]原 和 [1000 0000]原兩種編碼表示0;
[0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
// 用補(bǔ)碼運(yùn)算
// 0 用 [0000 0000]原 表示,-0 則不存在了,而且對于一個 8 位二進(jìn)制數(shù),
// 可以用 [1000 0000]原 表示 -128,即最小值。-128 沒有原碼和補(bǔ)碼表示。
[0000 0001]補(bǔ) + [1111 1111]補(bǔ) = [0000 0000]補(bǔ) = [0000 0000]原 = 0
從上面的運(yùn)算結(jié)果來看,只有用補(bǔ)碼運(yùn)算結(jié)果最準(zhǔn)確,下面進(jìn)一步用補(bǔ)碼運(yùn)算驗證:
6 - 5 = ?
6 的補(bǔ)碼為:0000 0110
+
-5 的補(bǔ)碼為:1111 1011
=
0000 0001
正數(shù) 3 種編碼方式相同,所以原碼也為 0000 0001,對應(yīng)十進(jìn)制為:1
-6 - 5 = ?
-6 補(bǔ)碼為:1111 1010
+
-5 補(bǔ)碼為:1111 1011
=
1111 0101
補(bǔ)碼為負(fù)數(shù),原碼為:1000 1011 ,對應(yīng)十進(jìn)制為:-11
小結(jié)
- 一個正數(shù)的原碼、反碼、補(bǔ)碼 3 種編碼方式結(jié)果相同,而對于一個負(fù)數(shù),3 種編碼方式是完全不同的。
- [負(fù)數(shù)]原 = 負(fù)數(shù)補(bǔ)碼再求補(bǔ)碼
- 在計算機(jī)內(nèi)部,二進(jìn)制加法是基本運(yùn)算,而二進(jìn)制的減法則是采用補(bǔ)碼運(yùn)算,將減法轉(zhuǎn)換成加法實現(xiàn)的;二進(jìn)制乘、除法運(yùn)算可以通過加、減和移位來實現(xiàn)。 二進(jìn)制數(shù)中小數(shù)點(diǎn)向右移 1 位,數(shù)值就擴(kuò)大2倍;小數(shù)點(diǎn)向左移 1 位,數(shù)值就縮小 2 倍。
位操作符
<<
左移位,移動位置為從左至右第一個出現(xiàn) 1 的位置開始,低位補(bǔ) 0,高位丟失。如需要了解移動過程,請參考下面的例子:
// 下面以數(shù)字 13 為例,因為 13 為正數(shù),所以原碼、反碼、補(bǔ)碼都相同,即:
// 13[原][反][補(bǔ)] = 0000 1101
// 例1:13 << 2
// 0000 1101 左移 2 位得到的結(jié)果為:0011 0100
// 很明顯,0011 0100 為一正數(shù),所以其對應(yīng)的原碼也是 0011 0100,對應(yīng)的十進(jìn)制為:52
System.out.println(13 << 2);// 輸出:52
// 例2:13 << 4
// 0000 1101 左移 4 位得到的結(jié)果補(bǔ)碼為:1101 0000
// 因為 1101 0000 是一正數(shù),所以其對應(yīng)的原碼也是 1101 0000,對應(yīng)的十進(jìn)制為:208
//
// 「 注意 」?。。。。。?// 1101 0000 中 的 1101 并不是處于最高位,它前面還有多位,只是我們?yōu)榱朔奖銢]寫出來。
// 下面一個例子將驗證最高位到底在哪一位
System.out.println(13 << 4);// 輸出:208
// 例3:13 << 28
// 首先,一個數(shù)有可能是32位、64位,我們猜測它是 32 位的,那么 13 的補(bǔ)碼就該寫為:
// 0000 0000 0000 0000 0000 0000 0000 1101
// 如果這個數(shù)是 32 位的,且 1101 要處于最高位,那么觀察上面的數(shù),至少需要左移 28 位,移動后為:
// 1101 0000 0000 0000 0000 0000 0000 0000
// 注意,移位后的這個數(shù)依然是一個補(bǔ)碼,如果我們猜測的 32 位數(shù)沒錯,那么最高位就應(yīng)該是符號位
// 左移 28 位后應(yīng)該就為一個負(fù)數(shù)了,其原碼為:
// 1011 0000 0000 0000 0000 0000 0000 0000,對應(yīng)的十進(jìn)制數(shù)為:-805306368
// 從下面的輸出來看,我們的猜測是正確的
//
// 「 注意 」!?。。。。?// 有可能和具體設(shè)備有關(guān),但難思路都相同
System.out.println(13 << 28);// 輸出:-805306368
// 例4:13 << 32
// 從上面的 例3 中可以后出,這是一個 32 位數(shù),如果我們向左移動 32 位會怎樣?
// 從輸出可以看出,其值為13,也就是說這個數(shù)又回到了正常的形式,即:
// 0000 0000 0000 0000 0000 0000 0000 1101
System.out.println(13 << 32);// 輸出:13
// 例5:-13 << 4
// -13 的補(bǔ)碼為:
// 1111 1111 1111 1111 1111 1111 1111 0011
// 左移 4 位后:
// 1111 1111 1111 1111 1111 1111 0011 0000
// 對應(yīng)原碼為:
// 1000 0000 0000 0000 0000 0000 1101 0000
// 對應(yīng)十進(jìn)制為:
// -208
System.out.println(-13 << 4); // 輸出:-208
>>
右移位,移動位置為從左至右第一個出現(xiàn) 1 的位置開始,高位根據(jù)符號位進(jìn)行補(bǔ)位,符號位是 0 補(bǔ) 0,是 1 補(bǔ) 1,低位丟失。例:
// 例1:-13 >> 4
// -13 的補(bǔ)碼為:
// 1111 1111 1111 1111 1111 1111 1111 0011
// 右移 4 位后:
// 1111 1111 1111 1111 1111 1111 1111 1111
// 對應(yīng)原碼:
// 1000 0000 0000 0000 0000 0000 0000 0001
// 對應(yīng)十進(jìn)制為:
// -1
System.out.println(-13 >> 4); // 輸出:-1
// 例2:13 >> 4
// 13 的補(bǔ)碼為:
// 0000 0000 0000 0000 0000 0000 0000 1101
// 右移 4 位后
// 0000 0000 0000 0000 0000 0000 0000 0000
// 對應(yīng)十進(jìn)制為:
// 0
System.out.println(13 >> 4);// 輸出:0
// 例3:7 >> 2
// 7 的補(bǔ)碼為:
// 0000 0000 0000 0000 0000 0000 0000 0111
// 右移 2 位后
// 0000 0000 0000 0000 0000 0000 0000 0001
// 對應(yīng)十進(jìn)制為:
// 1
System.out.println(7 >> 2);// 輸出:1
>>>
無符號右移,只對 32 位與 64 位數(shù)有意義。對于正數(shù),>> 與 >>> 結(jié)果都是一樣的; 負(fù)數(shù)時,>> 高位用 1 補(bǔ)上,>>> 高位用 0 補(bǔ)上。
& (位與運(yùn)算符)
只有 1 & 1 = 1,其它都為 0。例:
// 6 & 5
// 6對應(yīng)補(bǔ)碼為: 0000 0110
// &
// 5對應(yīng)補(bǔ)碼為: 0000 0101
// =
// 補(bǔ)碼結(jié)果為: 0000 0100
// 正數(shù)的 3 種編碼方式相同,所以 0000 0100 原碼也是 0000 0100 ,對應(yīng)十進(jìn)制為:4
| (位或運(yùn)算符)
只有 0 | 0 = 0,其它都為 1。例:
// 6 | 5
// 6對應(yīng)補(bǔ)碼為: 0000 0110
// |
// 5對應(yīng)補(bǔ)碼為: 0000 0101
// =
// 補(bǔ)碼運(yùn)算結(jié)果為: 0000 0111
// 正數(shù)的 3 種編碼方式相同,所以 0000 0111 的原碼也為 0000 0111,對應(yīng)的十進(jìn)制為:7
~ (位非運(yùn)算符)
0 變 1,1 變 0,且這個位運(yùn)算符是一個單目運(yùn)算符,不能寫成 x ~ y 的形式。例:
// 例 1:~-6
// -6 原碼為:
// 1000 0000 0000 0000 0000 0000 0000 0110
// 補(bǔ)碼為:
// 1111 1111 1111 1111 1111 1111 1111 1010
// 進(jìn)行位非運(yùn)算后:
// 0000 0000 0000 0000 0000 0000 0000 0101
// 正數(shù)原碼、反碼、補(bǔ)碼相同,所以對應(yīng)十進(jìn)制為:
// 5
^ (位異或運(yùn)算符)
1 ^ 0 = 1,其它都為 0。例:
// 6^5
// 6對應(yīng)補(bǔ)碼為: 0000 0110
// ^
// 5對應(yīng)補(bǔ)碼為: 0000 0101
// =
// 補(bǔ)碼運(yùn)算結(jié)果為: 0000 0011
// 正數(shù)的 3 種編碼方式相同,所以 0000 0011 的原碼也為 0000 0011,對應(yīng)的十進(jìn)制為:3
& 做邏輯運(yùn)算符與 && 的區(qū)別
不管 & 前面的表達(dá)式的結(jié)果為 true 或 false 后面的表達(dá)式都會參加計算。只要 && 前面的表達(dá)式為 false,則后面的表達(dá)式不參加運(yùn)算。例:
int i = 1;
boolean b = i++ > 5 & i++ < 10;
// 結(jié)果為:b = false;i = 3;
boolean bb = i++ > 5 && i++ < 10;
// 結(jié)果為:bb = false;i = 2;
| 做邏輯運(yùn)算符與 || 的區(qū)別
不管 | 前面的表達(dá)式的結(jié)果為 true 或 false 后面的表達(dá)式都會參加計算。
只要 || 前面的表達(dá)式為 true,則后面的表達(dá)式不參加運(yùn)算。例:
int i = 1;
boolean b = i++ < 5 | i++ < 10;
// 結(jié)果為:b = true;i = 3;
boolean bb = i++ < 5 || i++ < 10;
// 結(jié)果為:bb = true;i = 2;
位運(yùn)算用途
乘以、除以 2^n
x << n(乘以 2^n)
x >> n(除以 2^n)
判斷一個數(shù)是奇數(shù)還是偶數(shù)
x & 1(結(jié)果只有 0 和 1 兩種情況,0 為偶數(shù),1 為奇數(shù))
x & 2 (結(jié)果只有 0 和 2 兩種情況,可做兩種條件的判斷)
求兩個數(shù)的平均數(shù)
(x & y) + ( ( x ^ y ) >> 1)
兩個數(shù)交換
x ^= y;
y ^= x;
x ^= y;
求相反數(shù)
~x + 1
提高運(yùn)算效率
可提高運(yùn)算效率,處理器能夠直接支持
高級用法
權(quán)限管理、數(shù)據(jù)加密等,利用位運(yùn)算可做的東西深不可測。