1. 程式人生 > >神奇的位運算

神奇的位運算

eof googl 去掉 www 知識點 binarys 深入理解 網上 負數

位運算的威力



前言:

在學習Java二進制各種轉換時,發現對位運算很不熟悉,怪我基礎沒打好,更要好好學習了。然後從網上搜刮了一些位運算的小應用,然後有一些不熟悉的自己用Java代碼試了一下,發現真的很神奇,習慣了用普通的運算方法,對位運算一時半會還真的不習慣,看著式子也要思考一會才能想通,不過掌握了位運算對計算效率真的有很大的提升,大家不妨來看一下。

參考博文:http://blog.csdn.net/iukey/article/details/7195265


對於基本的位運算操作符,我在上一篇已經講解過了,不清楚的朋友請轉移到:http://www.cnblogs.com/hysum/p/7190388.html

對於基本的運算操作符有一個口訣獻給大家:

清零取數要用與,某位置一可用或

若要取反和交換,輕輕松松用異或


下面來看幾個具體的例子:

1) 判斷int型變量a是奇數還是偶數

a&1 = 0 偶數

a&1 = 1 奇數

1 //判斷int型變量a是奇數還是偶數
2         System.out.println("請輸入一個整數:");
3         Scanner in=new Scanner(System.in);
4         int a=in.nextInt();
5         if((a&1)==0) 
6             System.out.println("您輸入的是偶數!");
7 if((a&1)==1) 8 System.out.println("您輸入的是奇數!"); 9 System.out.println("您輸入的數二進制為:"+Integer.toBinaryString(a));

運行結果:

技術分享

2) 取int型變量a的第k位 (k=0,1,2……sizeof(int)),即a>>k&1 (先右移再與1)

3) 將int型變量a的第k位清0,即a=a&~(1<<k)(1左移取反再與a)

4) 將int型變量a的第k位置1,即a=a|(1<<k) (1左移或上a)

以上三個例子:

 1 System.out.println("請輸入一個整數:");
 2         Scanner in=new Scanner(System.in);
 3         int a=in.nextInt(); 5         System.out.println("您輸入的數二進制為:"+Integer.toBinaryString(a));
 6         System.out.println("取int型變量a的第2位:"+(a>>1&1));    
 7         a=a&~(1<<2);//將int型變量a的第3位清0
 8         System.out.println("將第3位清0後的二進制為:"+Integer.toBinaryString(a));
 9         a=a|(1<<2);//將int型變量a的第3位置1
10         System.out.println("將第3位置1後的二進制為:"+Integer.toBinaryString(a));

運行結果:

技術分享

7)對於一個數 x >= 0,判斷是不是2的冪。

1 boolean power2(int x){return ( (x&(x-1))==0) && (x!=0);}

8)不用temp交換兩個整數

1 public void swap(int x , int y)
2 {x ^= y;y ^= x;x ^= y;}

9)計算絕對值

1         //計算絕對值
2         int y ;
3         y = a >> 31;
4         System.out.println(y);  
5         System.out.println("絕對值為:"+ ((a^y)-y));  //or: (a+y)^y
6         

運行結果:

技術分享 技術分享

分析:可以看出y的值由a來決定,當a為正數y等於0,當a為負數y等於-1。知道了y的值的含義,那麽接下來計算絕對值的式子就不難理解了,對於正數來說兩個式子都是a^0等於a本身;對於負數來說,第一個式子(a^y)-y,可以看成先取反然後加上1(相當於原碼轉為補碼);第二個式子(a+y)^y,可以看成先減1然後取反(相當於補碼轉為原碼)。因為原碼和補碼是相對的,所以兩個式子的計算結果是一樣的。

10)取模運算轉化成位運算 (在不產生溢出的情況下,正數)

a % (2^n) 等價於 a & (2^n - 1)

a % 2 等價於 a & 1

11)乘法運算轉化成位運算 (在不產生溢出的情況下,正數)

a * (2^n) 等價於 a<< n

乘除2的倍數:千萬不要用乘除法,非常拖效率。只要知道左移1位就是乘以2,右移1位就是除以2就行了。比如要算25 * 4,用25 << 2就好啦

12)除法運算轉化成位運算 (在不產生溢出的情況下,正數)

a / (2^n) 等價於 a>> n

與乘法上述同理。

13) if (x == a) x= b else x= a;

  等價於 x= a ^ b ^ x;

14) x 的 相反數 表示為 (~x+1)


再來看兩個具體的應用,更深入理解位運算:

例1.子網掩碼

本人的網絡沒怎麽學,對於子網掩碼也是了解個大概,具體內容可以自己去google,等博主學好了會給大家分享相關內容的。下一篇博文:《了解網絡之子網掩碼詳解》

假如我是一個網管,公司內部使用C類地址,現在我要把公司網絡劃分成5個子網,網絡號為192.168.1.0的前三段,那麽子掩碼怎麽填呢?

子網的子網掩碼:192.168.1.224。(當然這裏還有其他答案,我取的是在子網擴充不超過6個的情況下的每個子網所容納主機最多的最佳方案)。

這個怎麽來的呢?

ip本身是個二進制位的,為了方便人們設置,我們采用了點分十進制的轉換,把32位的ip地址轉換成了4個字節的十進制萊表示。比如 192.168.1.213 這個ip地址的二進制表示為:11000000 10101000 00000001 11010101 。對於C類地址默認的前三個字節表示網絡號,那麽這個網絡號就是:11000000 10101000 00000001,最後一個字節11010101表示主機號,可以知道這個網絡可以容納的最多主機數為2^8-2,為什麽減2?因為主機號全為0的是網絡地址,不可用,主機號全為1的是廣播地址,也不可以分配給主機,所以要減去網絡地址和廣播地址,也就是減去2了。現在要劃分子網,那麽我們就要從表示主機的那個字節也就是8個位裏面拿出幾個位來表示子網號, 幾位比較合適呢?這就要看你需要劃分多少個子網咯。比如我們現在要劃分5個子網,(5)10 = (101)2 ,那麽至少就需要3位了,而且最多可以劃分2^3-2 = 6個子網,這裏減2的道理與主機相同。現在把224換成二進制看看吧(224)10 = (11100000)2 ,明白了吧,我們可以推斷出子網掩碼幹了什麽勾當?不錯子網掩碼與ip地址做了按位與運算,他的作用就是屏蔽了主機號獲取網絡號與子網號。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

例2. 防止int型變量溢出(C/C++)

1  int x = 32760;int y = 32762; 要求求x、y的平均值。
2 int ave(int x, int y)   //返回X、Y的平均值
3 {   
4      return (x & y) + ( (x^y)>>1 );
5 }

知識點:

>>n 相當於除於2^n ,<<n 相當於乘於2^n .

x,y對應位均為1,相加後再除以2還是原來的數,如兩個00001000相加後除以2仍得00001000,那麽我們把x與y分別分成兩個部分來看,兩者相同的位分別拿出來 則 :

x = (111111111111000)2 = (111111111111000)2 + (000000000000000)2

y = (111111111111010)2 = (111111111111000)2 + (000000000000010)2

相同部分我們叫做x1,y1,不同部分我們叫做x2,y2.那麽現在(x+y)/2 =(x1+y1)/2 +(x2 + y2)/2 ,因為x1 == y1 ,所以(x1+y1)/2 ==x1 ==y1,相同部分我們用與運算求出來 x1 = x&y ,不同部分的和我們用^求出來,然後除於2是不是我們想要的結果了呢?

為什麽要用位運算來求解呢?為了防止int型變量溢出!

我們都知道int型是有範圍的,它是一個32bit的整數類型,平常我們計算小的數據的平均數不需要用位運算,但是如果你要計算的數據你自己無法估計,但是只知道要計算的兩個數都是在int範圍裏的,如果我們用常規的方法計算這兩個數,它們的和超過了int的範圍則就會發生溢出。這裏用位運算就很好地避免了這個問題,因為在這個算法中用&運算來獲取兩個數相同的部分,相同的部分不需要再相加,用^運算來獲取倆者的不同的部分的1再除以2。通過二進制的形式很容易發現,這樣的做法是不會發生溢出的。保證了算法的可靠性。

這個例子有點難於理解.但是經過我的分解應該還算好理解了,弄懂這個例子相信你的位運算已經登入大門了。


下面我將網絡上找到的位運算的各種應用例子整理成了一個表格,供大家參考:

功能 示例 位運算
去掉最後一位 (101101->10110) x >> 1
在最後加一個0 (101101->1011010) x < < 1
在最後加一個1 (101101->1011011) x < < 1+1
把最後一位變成0 (101101->101100) x | 1-1
最後一位取反 (101101->101100) x ^ 1
把右數第k位變成1 (101001->101101,k=3) x | (1 < < (k-1))
把右數第k位變成0 (101101->101001,k=3) x & ~ (1 < < (k-1))
右數第k位取反 (101001->101101,k=3) x ^ (1 < < (k-1))
取末三位 (1101101->101) x & 7
取右數第k位 (1101101->1,k=4) x >> (k-1) & 1
把末k位變成1 (101001->101111,k=4) x | (1 < < k-1)
末k位取反 (101001->100110,k=4) x ^ (1 < < k-1)
把右邊連續的1變成0 (100101111->100100000) x & (x+1)
把右起第一個0變成1 (100101111->100111111) x | (x+1)
把右邊連續的0變成1 (11011000->11011111) x | (x-1)
取右邊連續的1 (100101111->1111) (x ^ (x+1)) >> 1
去掉右起第一個1的左邊 (100101000->1000) x & (x ^ (x-1))

結束語:想問問各位大佬,位運算在哪裏應用的最多?是不是C/C++這種面向底層的語言更多接觸到位運算?了解頗淺,希望能有個解答,謝謝!!

神奇的位運算