Hopes

          Start Here..

           

          求整數中比特為1的二進制位數 (轉)

          求整數中比特為1的二進制位數

          分類: C/C++ 2306人閱讀 評論(3) 收藏 舉報

           

          好幾次在CSDN上看到別人討論如何求出一個整數的二進制表示中狀態為1的比特位數。今天寫了個程序把從網上看來的加上自己想出來的共有5種方法測試了一下,覺得好玩,就寫了這篇博客。

          首先簡單介紹一下這五種方法。

          第一種:最簡單的,通過移位操作逐位測試并計數,不妨稱為“逐位測試法”;

          第二種:注意到對于“單比特二進制”而言,其狀態與數值“相同”。即對于單個比特的“數”而言,為0即表示數值0,“全部為1”即表示數值1(注意多比特數值顯然沒有這個特點,比如一個字節有8個比特,當8個比特全為1時并不代表整數8,而代表255)。利用這一特點,從單個比特開始,以相鄰的一位、兩位、四位、八位和十六位為分組,一組一組地相加并逐步累計,最終得出結果;不妨稱為“分組統計法”;

          第三種:注意到一個整數減1的會把最后一位1變成0,而其后的0全變成1,利用這一特點,把一個數不斷與它減1后的結果做“按位與”,直到它變成0,那么循環的次數便是其狀態為1的比特位數。不妨稱之為“循環減一相與法”;

          第四種:考虛到多數處理器都提供“找到第一個為1的比特位”這種指令,在我的PC上直接調用X86處理器的BSFBSR指令,每次直接找到第一個不為0的位并消掉,直到該數變成0,那么循環的次數即為狀態為1的比特位數。這種不妨稱為“匯編嵌入法”;

          第五種,一個字節一共有256種狀態,將每一個取值所對應的0比特位數的事先寫在程序中(注意這些數值是有規律的),也耗不了太多內存,然后程序運行的時候,把整數的四個字節拆開逐字節查表并相加,這種可稱為“查表法”。

           

          以下是程序。程序中對應以上五種方法的函數分別命名為f1f5。另外還有三個函數,correctness_test通過幾個簡單而又特殊的取值測試各個函數的正確性,相當于一個小單元測試;performance_test則分別將這5個函數作用于一億個隨機整數同進行性能測試;prepare_test_data則是準備1億個隨機整數的程序(這個函數實際并沒有為測試數據提供足夠的隨機性,但這一點對性能測試的結果應該沒有太大影響)

          1. #include <iostream>    
          2. #include <cstdlib>   
          3. #include <ctime>   
          4. using namespace std;   
          5.   
          6. int f1(unsigned int num);  
          7. int f2(unsigned int num);  
          8. int f3(unsigned int num);  
          9. int f4(unsigned int num);  
          10. int f5(unsigned int num);  
          11.   
          12. void correctness_test();  
          13. void performance_test();  
          14. void prepare_test_data(unsigned int data[], int size);  
          15.   
          16. int main() {  
          17.     correctness_test();  
          18.     performance_test();  
          19.     return 0;   
          20. }  
          21.   
          22. int f1(unsigned int num) {  
          23.     int count = 0;  
          24.     while(num) {  
          25.         if(num & 1) ++count;  
          26.         num >>= 1;  
          27.     }  
          28.     return count;  
          29. }  
          30.   
          31. int f2(unsigned int num) {  
          32.     const unsigned int M1 = 0x55555555;  
          33.     const unsigned int M2 = 0x33333333;  
          34.     const unsigned int M4 = 0x0f0f0f0f;  
          35.     const unsigned int M8 = 0x00ff00ff;  
          36.     const unsigned int M16 = 0x0000ffff;  
          37.   
          38.     num = (num & M1) + ((num >> 1) & M1);  
          39.     num = (num & M2) + ((num >> 2) & M2);  
          40.     num = (num & M4) + ((num >> 4) & M4);  
          41.     num = (num & M8) + ((num >> 8) & M8);  
          42.     num = (num & M16) + ((num >> 16) & M16);  
          43.     return num;  
          44. }  
          45.   
          46. int f3(unsigned int num) {  
          47.     int count = 0;  
          48.     while(num) {  
          49.         num &= (num - 1);  
          50.         ++count;  
          51.     }  
          52.     return count;  
          53. }  
          54.   
          55. int f4(unsigned int num) {  
          56.     int count = 0;  
          57.     while(num) {  
          58.         int n;  
          59.         __asm {  
          60.             bsr eax, num  
          61.             mov n, eax  
          62.         }  
          63.         ++count;  
          64.         num ^= (1 << n);  
          65.     }  
          66.     return count;  
          67. }  
          68.   
          69. int f5(unsigned int num) {  
          70.     static const signed char TABLE[256] = {   
          71.         0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,  
          72.         1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,  
          73.         1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,  
          74.         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,  
          75.         1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,  
          76.         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,  
          77.         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,  
          78.         3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,  
          79.         1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,  
          80.         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,  
          81.         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,  
          82.         3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,  
          83.         2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,  
          84.         3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,  
          85.         3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,  
          86.         4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8  
          87.     };  
          88.   
          89.     unsigned char* p = reinterpret_cast<unsigned char*>(&num);  
          90.     int count = 0;  
          91.     while(p != reinterpret_cast<unsigned char*>(&num + 1)) {  
          92.         count += TABLE[*p++];         
          93.     }  
          94.     return count;  
          95. }  
          96.   
          97. void correctness_test() {  
          98.     unsigned int test_data[] = {0, 1, 2, 3, 0x01234567, 0x89abcdef, 0xffffffff};  
          99.     unsigned int corect_result[] = {0, 1, 1, 2, 12, 20, 32};  
          100.   
          101.     int (*fn[])(unsigned int) = {f1, f2, f3, f4, f5};  
          102.     for(int i = 0; i < sizeof(fn) / sizeof(*fn); ++i) {  
          103.         for(int j = 0; j < sizeof(test_data) / sizeof(*test_data); ++j) {  
          104.             if(fn[i](test_data[j]) != corect_result[j]) {  
          105.                 cout << "f" << (i + 1) << " failed!" << endl;  
          106.                 exit(-1);  
          107.             }  
          108.         }  
          109.     }  
          110.     cout << "All methods passed correctness test." << endl;  
          111. }  
          112.   
          113. void performance_test() {  
          114.     const int TEST_DATA_SIZE = 100000000;  
          115.     unsigned int* test_data = new unsigned int[TEST_DATA_SIZE];  
          116.     prepare_test_data(test_data, TEST_DATA_SIZE);  
          117.   
          118.     int (*fn[])(unsigned int) = {f1, f2, f3, f4, f5};  
          119.     for(int i = 0; i < sizeof(fn) / sizeof(*fn); ++i) {  
          120.         clock_t start = clock();  
          121.         for(int j = 0; j < TEST_DATA_SIZE; ++j) {  
          122.             fn[i](test_data[j]);  
          123.         }  
          124.         int ticks = clock() - start;  
          125.         double seconds = ticks * 1.0 / CLOCKS_PER_SEC;  
          126.   
          127.         cout << "f" << (i + 1) << " consumed " << seconds << " seconds." << endl;  
          128.     }  
          129.     delete[] test_data;  
          130. }  
          131.   
          132. void prepare_test_data(unsigned int* data, int len) {  
          133.     srand(0);  
          134.     for(int i = 0; i < len; ++i) {  
          135.         data[i] = static_cast<unsigned int>(rand() * 1.0 / RAND_MAX * 0xffffffff);  
          136.     }  
          137. }  

           

          在我的機器上(AMD Phenom 8560處理器,Windows XP SP2),使用Visual C++ 2008 Express Edition編譯并運行(Release版),某一次得到的輸出如下:

          All methods passed correctness test.

          f1 consumed 14.156 seconds.

          f2 consumed 1.032 seconds.

          f3 consumed 4.656 seconds.

          f4 consumed 13.687 seconds.

          f5 consumed 1.422 seconds.

          從結果來看,最慢的是第一種“逐位測試法”,最快的是第二種“分組統計法”。兩者相差近14倍!

          第三種“循環減一相與法”表現也很不錯,雖然跟最快的相比遜色很多,但比最慢的強多了;

          第四種“匯編嵌入法”,表面上看,其復雜度是跟數值中1的位數相關,這一點與方法三一樣。而不像方法一那樣復雜度跟整個數的位數有關。但其表現并不令人滿意,結果幾乎跟方法一一樣差,而無法跟方法三相比。查了一下指令手冊,發現BSR指令并不是一條固定周期的指令,作用于32位整數時,快的時候它只需幾個CPU時鐘周期,慢的時候需要40幾個時鐘周期,我想它極有可能是在CPU內部通過類似于“逐位查找”的微命令實現的。

          第五種“查表法”的表現倒讓人相當滿意,性能逼近方法二。注意我只用了基于字節編碼的表,如果實際使用中需要這種運算,我們完全可以構造一個基于unsigned short編碼的表,這樣一個表將占用64K內存,在現代PC上仍屬小菜一碟,而每個32位數只需要把前后兩半查表的結果一加即可。那樣一來,其性能會不會比方法二還要強呢?有興趣的朋友可以試試。:P

          最后,或許有朋友會問:第四種方法中既然采用嵌入匯編,為何不把整個函數都用匯編來寫呢?那樣或許效率還會好一些。但那對其它幾種方法來說是不公平的,因為所有的方法都可以改用匯編來寫。所以,在這個函數中我只是把依賴于特定處理器(X86)、且無法使用C++語言及其標準庫直接實現的部分用匯編實現,其它的計算仍然用C++語言寫出。剩下的事情,跟其它幾種方法的實現一樣——讓編譯器看著辦吧,它愛怎么優化就怎么優化。

          posted on 2013-11-11 18:57 ** 閱讀(230) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           

          導航

          統計

          公告

          你好!

          常用鏈接

          留言簿(2)

          隨筆檔案

          文章分類

          文章檔案

          新聞檔案

          相冊

          收藏夾

          C#學習

          友情鏈接

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 嘉定区| 台南市| 石柱| 凌海市| 兰考县| 大荔县| 南京市| 利川市| 南江县| 乌鲁木齐市| 长子县| 罗山县| 黄冈市| 佛坪县| 商都县| 双辽市| 达拉特旗| 武穴市| 泰来县| 武乡县| 庆阳市| 贺州市| 修水县| 大田县| 永清县| 西和县| 莱州市| 石泉县| 溆浦县| 远安县| 桦甸市| 吉首市| 平安县| 唐河县| 葵青区| 大同县| 肥城市| 吴川市| 通江县| 施甸县| 棋牌|