2的n次方都有一個特性,在2進制中,只有1個1。
比如 1,10,100,1000,分別對應1,2,4,8。
16 | 8 | 4 | 2 | 1 |
0 | 0 | 0 | 0 | 1 |
16 | 8 | 4 | 2 | 1 |
0 | 0 | 0 | 1 | 0 |
16 | 8 | 4 | 2 | 1 |
0 | 0 | 1 | 0 | 0 |
以此類推,假如1個2進制數中,只有1個1,那就是2的n次方。
那知道了這個特性,又怎麼確定呢?
我們來看看有哪些位運算能幫到我們
符號 | 描述 | 運算規則 |
---|---|---|
& | 與 | 兩個位都爲1時,結果才爲1,否則爲0 |
| | 或 | 兩個位都爲0時,結果才爲0,否則爲1 |
^ | 異或 | 兩個位相同爲0,相異爲1 |
~ | 取反 | 0變1,1變0 |
<< | 左移 | 各二進位全部左移若干位,高位丟棄,低位補0 |
>> | 右移 |
各二進位全部右移若干位, 對無符號數,高位補0, 有符號數,各編譯器處理方法不一樣,有的補符號位(算術右移),有的補0(邏輯右移) |
一、&運算
兩個位都爲1時,結果才爲1,否則爲0
例1 例2
001001 (9) 001111 (15)
& 011111 (31) & 000000 (0)
001001 = 9 000000 = 0
例3 例4
101001 (41) 101110 (46)
& 010111 (23) & 001100 (12)
000001 = 1 001100 = 12
二、|運算
兩個位都爲0時,結果才爲0,否則爲1
例1 例2
001001 (9) 001111 (15)
| 011111 (31) | 000000 (0)
011111 = 31 001111 = 15
例3 例4
101001 (41) 101110 (46)
| 010111 (23) | 001100 (12)
000001 = 1 001100 = 12
^運算
兩個位相同爲0,相異爲1
例1 例2
001001 (9) 001111 (15)
^ 011111 (31) ^ 000000 (0)
010110 = 22 001111 = 15
例3 例4
101001 (41) 101110 (46)
^ 010111 (23) ^ 001100 (12)
111110 = 62 100010 = 34
~運算
取反,0變1,1變0
計算機存儲數據是以二進制的補碼形式來存儲的,正數的補碼是它本身(如:有二進制00000110,因爲他的第一位是0,即代表是正數,反碼、補碼就是它本身);
負數的補碼是它的反碼加1,也就是你說的‘取反加一’(如:有二進制10000110,第一位是1,代表它是負數,反碼就是每一位都取反,爲01111001,所以,補碼就是01111010)。
例1 例2
符號位 符號位
~ 0 0001001 (+9) ~ 1 0010000 (-15的補碼)
1 0001010 = -10 0 0001110 = +14
<<運算
數學意義:在數字沒有溢出的前提下,對於正數和負數,左移一位都相當於乘以2的1次方,左移n位就相當於乘以2的n次方。
2 << 2 = 8 3 << 2 = 12
10 (2) -> 1000 (8) 11 (3) -> 1100 (12)
>>運算
>>,有符號右移位,將運算數的二進制整體右移指定位數,整數高位用0補齊,負數高位用1補齊(保持負數符號不變)。
數學意義:在數字沒有溢出的前提下,對於正數和負數,右移一位代表除以2的1次方,右移N位代表除以2的N次方。
2 >> 1 = 1 9 >> 2 = 2
10 (2) -> 1 (1) 1001 (9) -> 10 (2)
分析題幹判斷2的n次方,肯定不能使用>>和<<對該數直接操作,一旦溢出,結果就不準確了。
剛纔也說了2的n次方,只有一個1。那麼這個數減去一個1,二進制就全是1。
比如:1000 - 1 = 111 100 - 1 = 11 10000 - 1 = 1111
利用好這個特性,從&看,兩個位都爲1時,結果才爲1,否則爲0
1000 100
& 0111 & 011
———— ————
0 0
那麼我們找到了答案,設要求的數爲a,那麼 a & (a-1) 如果等於0,那麼a一定是2的n次方
/// <summary>
/// 判斷一個數是否爲2的n次方
/// </summary>
/// <param name="n">欲求的數</param>
/// <returns>true=是,false=不是</returns>
public static bool is2pow(int n) {
return (n & (n - 1)) == 0;
}
舉一反三,如果要使用|來求解呢,我們可以發現結果全是1,那聯想到剛纔的^運算,是不是也全是1。
(兩個位都爲0時,結果才爲0,否則爲1)
1000 100
| 0111 | 011
———— ————
1111 111
(兩個位相同爲0,相異爲1)
1000 100
^ 0111 ^ 011
———— ————
1111 111
所以我們這麼看 n |(n-1) == n^(n-1)成立的話,是不是也可以判斷該數爲2的n次方呢,雖然看起來效率要低得多。
/// <summary>
/// 判斷一個數是否爲2的n次方
/// </summary>
/// <param name="n">欲求的數</param>
/// <returns>true=是,false=不是</returns>
public static bool is2pow_2(int n)
{
return (n | (n - 1)) == (n ^ ( n - 1)) ;
}
驗證猜想
哈哈,過程是辛苦的,結果是讓人欣慰的。
這就完啦?再來想一想還有沒有其他方法呢?
回到剛纔第一個方法,a & (a-1) == 0 可以判斷,其實還可以簡化一下,初中數學,左右同時加a,減a,得出 n == (n & -n)
於是咱們又得到一條新的公式,這個省去了0,比第一條效率高。
/// <summary>
/// 判斷一個數是否爲2的n次方
/// </summary>
/// <param name="n">欲求的數</param>
/// <returns>true=是,false=不是</returns>
public static bool is2pow_3(int n)
{
return n == (n & -n);
}
看到了這個結果,我們發現了-n,好像想到了啥,!!!剛纔的取反符號!
來吧,基於此,又來一條效率不怎麼樣的公式
/// <summary>
/// 判斷一個數是否爲2的n次方
/// </summary>
/// <param name="n">欲求的數</param>
/// <returns>true=是,false=不是</returns>
public static bool is2pow_4(int n)
{
return (n & (~n + 1)) == n;
}
再來驗證,以上公式3和公式4是否正確。
驗證猜想
紅紅火火恍恍惚惚~~~~
結尾
還有位移運算,雖然剛纔說不能直接對數進行左右位移運算,但咱可以新建一個數,讓它左移,每左移動一次就對該數進行判斷,相等就說明是2的次方。
其實拋開位運算,數學的方法可以一直除以2,如果結果不等於1,而且期間餘數不等於0就說明不是2的n次方,還有很多方法,比如就計算成二進制字符串,再看看是不是隻有一個1。又或者int類型中,二進制的長度是有限的,2的次方就那幾個,咱給做成數組,一個一個判斷。等等等等。