1. 程式人生 > 實用技巧 >位運算整理(2)

位運算整理(2)

從一個固定的位寬延伸出來的符號

對於內建型別,如chars和ints,符號擴充套件是自動的。但是,假設你有一個有符號的二補數x,只用b位儲存。此外,假設你想將x轉換為一個int,它的位數超過b位。如果x是正數,簡單的複製就可以了,但如果是負數,則必須擴充套件符號。例如,如果我們只有4位來儲存一個數字,那麼-3在二進位制中表示為1101。如果我們有8位,那麼-3就是1111101。當我們轉換到有更多位元的表示法時,4位元表示法中最重要的位元會被正向複製以填入目標位,這就是符號擴充套件。在C語言中,從一個恆定的位寬進行符號擴充套件是很簡單的,因為位域可以在結構或聯合中指定。例如,要從5位轉換為一個完整的整數。

int x; // convert this from using 5 bits to a full int
int r; // resulting sign extended number goes here
struct {signed int x:5;} s;
r = s.x = x;

下面是一個C++模板函式,它使用同樣的語言特徵,在一次操作中從B位進行轉換(當然編譯器生成的更多)

template <typename T, unsigned B>
inline T signextend(const T x)
{
  struct {T x:B;} s;
  return s.x = x;
}

int r = signextend<signed int,5>(x);  // sign extend 5 bit number x to r

2006年3月4日,Pat Wood指出,ANSI C標準要求位域必須有關鍵字 "signed "才能被簽名,否則,簽名是未定義的。

從一個可變位寬的標誌延伸出來

有時我們需要擴充套件一個數字的符號,但我們不知道先驗地知道它的位數,b,用它來表示。(或者我們可能是在像Java這樣的語言中進行程式設計,而這種語言缺乏位數字段。)

unsigned b; // number of bits representing the number in x
int x;      // sign extend this b-bit number to r
int r;      // resulting sign-extended number
int const m = 1U << (b - 1); // mask can be pre-computed if b is fixed

x = x & ((1U << b) - 1);  // (Skip this if bits in x above position b are already zero.)
r = (x ^ m) - m;

上面的程式碼需要四次操作,但當位寬是常數而不是變數時,只需要兩次快速操作,假設上位已經是零。

一種稍快但不太方便攜帶的方法,它不依賴於位置b以上的x中的位數為零

int const m = CHAR_BIT * sizeof(x) - b;
r = (x << m) >> m;

在3次操作中從可變位寬延伸出的符號

下面的內容在某些機器上可能會很慢,因為乘法和除法需要費力。這個版本是4次操作。如果你知道你的初始位寬b大於1,你可以通過使用r = (x * multipliers[b]) / multipliers[b]在3次操作中完成這種符號擴充套件,這隻需要一次陣列查詢。

 1 unsigned b; // number of bits representing the number in x
 2 int x;      // sign extend this b-bit number to r
 3 int r;      // resulting sign-extended number
 4 #define M(B) (1U << ((sizeof(x) * CHAR_BIT) - B)) // CHAR_BIT=bits/byte
 5 static int const multipliers[] = 
 6 {
 7   0,     M(1),  M(2),  M(3),  M(4),  M(5),  M(6),  M(7),
 8   M(8),  M(9),  M(10), M(11), M(12), M(13), M(14), M(15),
 9   M(16), M(17), M(18), M(19), M(20), M(21), M(22), M(23),
10   M(24), M(25), M(26), M(27), M(28), M(29), M(30), M(31),
11   M(32)
12 }; // (add more if using more than 64 bits)
13 static int const divisors[] = 
14 {
15   1,    ~M(1),  M(2),  M(3),  M(4),  M(5),  M(6),  M(7),
16   M(8),  M(9),  M(10), M(11), M(12), M(13), M(14), M(15),
17   M(16), M(17), M(18), M(19), M(20), M(21), M(22), M(23),
18   M(24), M(25), M(26), M(27), M(28), M(29), M(30), M(31),
19   M(32)
20 }; // (add more for 64 bits)
21 #undef M
22 r = (x * multipliers[b]) / divisors[b];

下面的變化是不可移植的,但在採用算術右移的架構上,保持符號,應該是很快的。

const int s = -b; // OR:  sizeof(x) * CHAR_BIT - b;
r = (x << s) >> s;

Randal E. Bryant 在 2005 年 5 月 3 日指出了早期版本中的一個錯誤 (該版本使用乘數[]來表示除數[]),它在 x=1 和 b=1 的情況下失敗。

有條件地設定或清除沒有分支的位

1 bool f;         // conditional flag
2 unsigned int m; // the bit mask
3 unsigned int w; // the word to modify:  if (f) w |= m; else w &= ~m; 
4 
5 w ^= (-f ^ w) & m;
6 
7 // OR, for superscalar CPUs:
8 w = (w & ~m) | (-f & m);

有條件地否定一個沒有分支的值

如果你只需要在一個標誌為假的時候進行否定,那麼使用下面的方法來避免分支。

 1 bool fDontNegate;  // Flag indicating we should not negate v.
 2 int v;             // Input value to negate if fDontNegate is false.
 3 int r;             // result = fDontNegate ? v : -v;
 4 
 5 r = (fDontNegate ^ (fDontNegate - 1)) * v;
 6 If you need to negate only when a flag is true, then use this:
 7 bool fNegate;  // Flag indicating if we should negate v.
 8 int v;         // Input value to negate if fNegate is true.
 9 int r;         // result = fNegate ? -v : v;
10 
11 r = (v ^ -fNegate) + fNegate;

根據掩碼合併兩個值的位數

1 unsigned int a;    // value to merge in non-masked bits
2 unsigned int b;    // value to merge in masked bits
3 unsigned int mask; // 1 where bits from b should be selected; 0 where from a.
4 unsigned int r;    // result of (a & ~mask) | (b & mask) goes here
5 
6 r = a ^ ((a ^ b) & mask); 

這就從根據位掩碼組合兩組位元的明顯方式中減去了一個操作。如果掩碼是一個常數,那麼可能沒有任何優勢。

計數位集

unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v

for (c = 0; v; v >>= 1)
{
  c += v & 1;
}

方法要求每一個位進行一次迭代,直到沒有更多的位被設定。所以在一個32位的字上,只有高位被設定,它將經歷32次迭代。

由查詢表設定的計數位

 1 static const unsigned char BitsSetTable256[256] = 
 2 {
 3 #   define B2(n) n,     n+1,     n+1,     n+2
 4 #   define B4(n) B2(n), B2(n+1), B2(n+1), B2(n+2)
 5 #   define B6(n) B4(n), B4(n+1), B4(n+1), B4(n+2)
 6     B6(0), B6(1), B6(1), B6(2)
 7 };
 8 
 9 unsigned int v; // count the number of bits set in 32-bit value v
10 unsigned int c; // c is the total bits set in v
11 
12 // Option 1:
13 c = BitsSetTable256[v & 0xff] + 
14     BitsSetTable256[(v >> 8) & 0xff] + 
15     BitsSetTable256[(v >> 16) & 0xff] + 
16     BitsSetTable256[v >> 24]; 
17 
18 // Option 2:
19 unsigned char * p = (unsigned char *) &v;
20 c = BitsSetTable256[p[0]] + 
21     BitsSetTable256[p[1]] + 
22     BitsSetTable256[p[2]] +    
23     BitsSetTable256[p[3]];
24 
25 
26 // To initially generate the table algorithmically:
27 BitsSetTable256[0] = 0;
28 for (int i = 0; i < 256; i++)
29 {
30   BitsSetTable256[i] = (i & 1) + BitsSetTable256[i / 2];
31 }

計數位集,布萊恩-克尼漢的方式

unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v
for (c = 0; v; c++)
{
  v &= v - 1; // clear the least significant bit set
}

Brian Kernighan 的方法是有多少個設定位就有多少次迭代。因此,如果我們有一個32位的字,只有高位被設定,那麼它將只通過一次迴圈。

使用64位指令對14、24或32位字中設定的位進行計數

 1 unsigned int v; // count the number of bits set in v
 2 unsigned int c; // c accumulates the total bits set in v
 3 
 4 // option 1, for at most 14-bit values in v:
 5 c = (v * 0x200040008001ULL & 0x111111111111111ULL) % 0xf;
 6 
 7 // option 2, for at most 24-bit values in v:
 8 c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
 9 c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) 
10      % 0x1f;
11 
12 // option 3, for at most 32-bit values in v:
13 c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
14 c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) % 
15      0x1f;
16 c += ((v >> 24) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;

這種方法需要64位的CPU和快速的模數劃分來提高效率。第一個選項只需要3次操作;第二個選項需要10次;第三個選項需要15次。

計數位設定,並行

 1 unsigned int v; // count bits set in this (32-bit value)
 2 unsigned int c; // store the total here
 3 static const int S[] = {1, 2, 4, 8, 16}; // Magic Binary Numbers
 4 static const int B[] = {0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF, 0x0000FFFF};
 5 
 6 c = v - ((v >> 1) & B[0]);
 7 c = ((c >> S[1]) & B[1]) + (c & B[1]);
 8 c = ((c >> S[2]) + c) & B[2];
 9 c = ((c >> S[3]) + c) & B[3];
10 c = ((c >> S[4]) + c) & B[4];

B陣列,用二進位制表示,是:

B[0] = 0x55555555 = 01010101 01010101 01010101 01010101
B[1] = 0x33333333 = 00110011 00110011 00110011 00110011
B[2] = 0x0F0F0F0F = 00001111 00001111 00001111 00001111
B[3] = 0x00FF00FF = 00000000 11111111 00000000 11111111
B[4] = 0x0000FFFF = 00000000 00000000 11111111 11111111

我們可以對更大的整數大小的方法進行調整,繼續使用二進位制魔數B和S的模式,如果有k位,那麼我們需要陣列S和B的元素長為ceil(lg(k)),我們必須計算與S或B長相同的c的表示式數量。對於32位的v,要用到16次運算。

32位整數v的最佳計數方法是以下:

v = v - ((v >> 1) & 0x55555555);                    // reuse input as temporary
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);     // temp
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; // count

最好的位數計算方法只需要12次操作,這與查詢表法相同,但避免了表的記憶體和潛在的快取遺漏。它是上述純並行方法和前面使用乘法的方法的混合體(在用64位指令計數位的部分),不過它沒有使用64位指令。對位元組中設定的位的計數是並行完成的,通過乘以0x1010101並右移24位,計算出位元組中設定的位的總和。

最好的位數計算方法對位寬不超過128的整數(引數化為T型)的概括是這樣的:

v = v - ((v >> 1) & (T)~(T)0/3);                           // temp
v = (v & (T)~(T)0/15*3) + ((v >> 2) & (T)~(T)0/15*3);      // temp
v = (v + (v >> 4)) & (T)~(T)0/255*15;                      // temp
c = (T)(v * ((T)~(T)0/255)) >> (sizeof(T) - 1) * CHAR_BIT; // count

從最重要的位到給定位置的計數位設定(等級)

下面查詢一個位的等級,即返回從最重要的位到給定位置的位的位數之和。

 1   uint64_t v;       // Compute the rank (bits set) in v from the MSB to pos.
 2   unsigned int pos; // Bit position to count bits upto.
 3   uint64_t r;       // Resulting rank of bit at pos goes here.
 4 
 5   // Shift out bits after given position.
 6   r = v >> (sizeof(v) * CHAR_BIT - pos);
 7   // Count set bits in parallel.
 8   // r = (r & 0x5555...) + ((r >> 1) & 0x5555...);
 9   r = r - ((r >> 1) & ~0UL/3);
10   // r = (r & 0x3333...) + ((r >> 2) & 0x3333...);
11   r = (r & ~0UL/5) + ((r >> 2) & ~0UL/5);
12   // r = (r & 0x0f0f...) + ((r >> 4) & 0x0f0f...);
13   r = (r + (r >> 4)) & ~0UL/17;
14   // r = r % 255;
15   r = (r * (~0UL/255)) >> ((sizeof(v) - 1) * CHAR_BIT);

選擇給定的位數(等級)的位數位置(從最重要的位)

下面的64位程式碼在從左數時選擇第r 1位的位置。換句話說,如果我們從最有意義的位開始,然後向右進行,計數設定為1的位數,直到達到所需的位數r,那麼我們停止的位置將被返回。如果要求的等級超過了設定的位數,則返回64。該程式碼可以修改為32位或從右開始計數。

 1   uint64_t v;          // Input value to find position with rank r.
 2   unsigned int r;      // Input: bit's desired rank [1-64].
 3   unsigned int s;      // Output: Resulting position of bit with rank r [1-64]
 4   uint64_t a, b, c, d; // Intermediate temporaries for bit count.
 5   unsigned int t;      // Bit count temporary.
 6 
 7   // Do a normal parallel bit count for a 64-bit integer,                     
 8   // but store all intermediate steps.                                        
 9   // a = (v & 0x5555...) + ((v >> 1) & 0x5555...);
10   a =  v - ((v >> 1) & ~0UL/3);
11   // b = (a & 0x3333...) + ((a >> 2) & 0x3333...);
12   b = (a & ~0UL/5) + ((a >> 2) & ~0UL/5);
13   // c = (b & 0x0f0f...) + ((b >> 4) & 0x0f0f...);
14   c = (b + (b >> 4)) & ~0UL/0x11;
15   // d = (c & 0x00ff...) + ((c >> 8) & 0x00ff...);
16   d = (c + (c >> 8)) & ~0UL/0x101;
17   t = (d >> 32) + (d >> 48);
18   // Now do branchless select!                                                
19   s  = 64;
20   // if (r > t) {s -= 32; r -= t;}
21   s -= ((t - r) & 256) >> 3; r -= (t & ((t - r) >> 8));
22   t  = (d >> (s - 16)) & 0xff;
23   // if (r > t) {s -= 16; r -= t;}
24   s -= ((t - r) & 256) >> 4; r -= (t & ((t - r) >> 8));
25   t  = (c >> (s - 8)) & 0xf;
26   // if (r > t) {s -= 8; r -= t;}
27   s -= ((t - r) & 256) >> 5; r -= (t & ((t - r) >> 8));
28   t  = (b >> (s - 4)) & 0x7;
29   // if (r > t) {s -= 4; r -= t;}
30   s -= ((t - r) & 256) >> 6; r -= (t & ((t - r) >> 8));
31   t  = (a >> (s - 2)) & 0x3;
32   // if (r > t) {s -= 2; r -= t;}
33   s -= ((t - r) & 256) >> 7; r -= (t & ((t - r) >> 8));
34   t  = (v >> (s - 1)) & 0x1;
35   // if (r > t) s--;
36   s -= ((t - r) & 256) >> 8;
37   s = 65 - s;

如果你的目標CPU上的分支速度很快,可以考慮取消對if-statements的註釋,並對它們後面的行進行註釋。

以天真的方式計算奇偶性

unsigned int v;       // word value to compute the parity of
bool parity = false;  // parity will be the parity of v

while (v)
{
  parity = !parity;
  v = v & (v - 1);
}

上面的程式碼使用了類似上面Brian Kernigan的位數計算的方法。它所需要的時間與設定的位數成正比。

通過查詢表計算奇偶性

 1 static const bool ParityTable256[256] = 
 2 {
 3 #   define P2(n) n, n^1, n^1, n
 4 #   define P4(n) P2(n), P2(n^1), P2(n^1), P2(n)
 5 #   define P6(n) P4(n), P4(n^1), P4(n^1), P4(n)
 6     P6(0), P6(1), P6(1), P6(0)
 7 };
 8 
 9 unsigned char b;  // byte value to compute the parity of
10 bool parity = ParityTable256[b];
11 
12 // OR, for 32-bit words:
13 unsigned int v;
14 v ^= v >> 16;
15 v ^= v >> 8;
16 bool parity = ParityTable256[v & 0xff];
17 
18 // Variation:
19 unsigned char * p = (unsigned char *) &v;
20 parity = ParityTable256[p[0] ^ p[1] ^ p[2] ^ p[3]];

使用64位乘法和模數除法計算位元組的奇偶性

unsigned char b;  // byte value to compute the parity of
bool parity = 
  (((b * 0x0101010101010101ULL) & 0x8040201008040201ULL) % 0x1FF) & 1;

上面的方法大約需要4次操作,但只對位元組有效。

用乘法計算字的奇偶性

下面的方法用乘法只用8次操作就計算出32位值的奇偶性。

1     unsigned int v; // 32-bit word
2     v ^= v >> 1;
3     v ^= v >> 2;
4     v = (v & 0x11111111U) * 0x11111111U;
5     return (v >> 28) & 1;

同樣是64位,8次操作還是足夠的。

    unsigned long long v; // 64-bit word
    v ^= v >> 1;
    v ^= v >> 2;
    v = (v & 0x1111111111111111UL) * 0x1111111111111111UL;
    return (v >> 60) & 1;

平行計算奇偶性

unsigned int v;  // word value to compute the parity of
v ^= v >> 16;
v ^= v >> 8;
v ^= v >> 4;
v &= 0xf;
return (0x6996 >> v) & 1;

上面的方法需要大約9次操作,並且適用於32位的字。通過刪除 "unsigned int v; "後面的兩行程式碼,可以優化為只對位元組進行5次操作。該方法首先將32位值的8個字頭一起移位和XOR,將結果留在v的最低字頭中。接下來,二進位制數0110 1001 1001 0110(十六進位制中的0x6996)被v的最低字頭所代表的值向右移位,這個數字就像一個微型的16位奇偶表,以v中的低4位為索引。

用減法和加法交換數值

#define SWAP(a, b) ((&(a) == &(b)) || \
                    (((a) -= (b)), ((b) += (a)), ((a) = (b) - (a))))

這將不使用臨時變數來交換a和b的值。當你知道這不可能發生時,可以省略a和b在記憶體中的同一位置的初始檢查。(編譯器可能會作為一種優化而省略它)。(編譯器可能會作為一種優化而省略它。)如果你啟用了溢位異常,那麼傳遞無符號值,這樣就不會丟擲異常。下面的XOR方法在某些機器上可能會稍微快一些。不要對浮點數使用這種方法(除非你對它們的原始整數表示進行操作)

用XOR交換值

#define SWAP(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b)))

這是一個古老的技巧,可以交換變數a和b的值,而不需要為臨時變數使用額外的空間。

用XOR交換單個位

unsigned int i, j; // positions of bit sequences to swap
unsigned int n;    // number of consecutive bits in each sequence
unsigned int b;    // bits to swap reside in b
unsigned int r;    // bit-swapped result goes here

unsigned int x = ((b >> i) ^ (b >> j)) & ((1U << n) - 1); // XOR temporary
r = b ^ ((x << i) | (x << j));

作為一個交換位元範圍的例子,假設我們有b=00101111(用二進位制表示),我們想把從i=1(右起第二位)開始的n=3個連續位元與從j=5開始的3個連續位元交換;結果將是r=11100011(二進位制)。

這種交換方法類似於通用的XOR交換技巧,但旨在對單個位進行操作。 變數x儲存了我們要交換的位元值對的XOR結果,然後位元被設定為自己與x的XOR結果。當然,如果序列重疊,結果是未定義的。

用明顯的方法反轉位

 1 unsigned int v;     // input bits to be reversed
 2 unsigned int r = v; // r will be reversed bits of v; first get LSB of v
 3 int s = sizeof(v) * CHAR_BIT - 1; // extra shift needed at end
 4 
 5 for (v >>= 1; v; v >>= 1)
 6 {   
 7   r <<= 1;
 8   r |= v & 1;
 9   s--;
10 }
11 r <<= s; // shift when v's highest bits are zero

通過查詢表反轉字中的位

 1 static const unsigned char BitReverseTable256[256] = 
 2 {
 3 #   define R2(n)     n,     n + 2*64,     n + 1*64,     n + 3*64
 4 #   define R4(n) R2(n), R2(n + 2*16), R2(n + 1*16), R2(n + 3*16)
 5 #   define R6(n) R4(n), R4(n + 2*4 ), R4(n + 1*4 ), R4(n + 3*4 )
 6     R6(0), R6(2), R6(1), R6(3)
 7 };
 8 
 9 unsigned int v; // reverse 32-bit value, 8 bits at time
10 unsigned int c; // c will get v reversed
11 
12 // Option 1:
13 c = (BitReverseTable256[v & 0xff] << 24) | 
14     (BitReverseTable256[(v >> 8) & 0xff] << 16) | 
15     (BitReverseTable256[(v >> 16) & 0xff] << 8) |
16     (BitReverseTable256[(v >> 24) & 0xff]);
17 
18 // Option 2:
19 unsigned char * p = (unsigned char *) &v;
20 unsigned char * q = (unsigned char *) &c;
21 q[3] = BitReverseTable256[p[0]]; 
22 q[2] = BitReverseTable256[p[1]]; 
23 q[1] = BitReverseTable256[p[2]]; 
24 q[0] = BitReverseTable256[p[3]];

第一種方法大約需要17次操作,第二種方法大約需要12次操作,假設你的CPU可以輕鬆載入和儲存位元組。

用3個操作(64位乘法和模數除法)反轉一個位元組中的位

unsigned char b; // reverse this (8-bit) byte
 
b = (b * 0x0202020202ULL & 0x010884422010ULL) % 1023;

乘法運算為8位位元組模式建立5個獨立的副本,以扇出成一個64位的值。AND操作選擇相對於每個10位位元組的正確(反向)位置的位元。乘法和AND操作將原始位元組中的位複製出來,使它們各自只出現在10位組中的一個位中。原始位元組中的位元的反轉位置與它們在任何10位組中的相對位置一致。最後一步,涉及模數除以2^10 - 1,效果是將64位值中的每一組10位(從0-9,10-19,20-29,...的位置)合併在一起。它們並不重疊,所以模數除法所依據的加法步驟就像或運算一樣。

用4次操作將一個位元組中的位數反轉(64位乘,不除)

unsigned char b; // reverse this byte
 
b = ((b * 0x80200802ULL) & 0x0884422110ULL) * 0x0101010101ULL >> 32;

下面顯示了布林變數a、b、c、d、e、f、g和h的位值流程,這些變數組成了一個8位位元組。請注意第一個乘法是如何將位元模式扇出到多個副本,而最後一個乘法是如何將它們合併到右邊第五個位元組中。

 1                                                                                         abcd efgh (-> hgfe dcba)
 2 *                                                      1000 0000  0010 0000  0000 1000  0000 0010 (0x80200802)
 3 -------------------------------------------------------------------------------------------------
 4                                             0abc defg  h00a bcde  fgh0 0abc  defg h00a  bcde fgh0
 5 &                                           0000 1000  1000 0100  0100 0010  0010 0001  0001 0000 (0x0884422110)
 6 -------------------------------------------------------------------------------------------------
 7                                             0000 d000  h000 0c00  0g00 00b0  00f0 000a  000e 0000
 8 *                                           0000 0001  0000 0001  0000 0001  0000 0001  0000 0001 (0x0101010101)
 9 -------------------------------------------------------------------------------------------------
10                                             0000 d000  h000 0c00  0g00 00b0  00f0 000a  000e 0000
11                                  0000 d000  h000 0c00  0g00 00b0  00f0 000a  000e 0000
12                       0000 d000  h000 0c00  0g00 00b0  00f0 000a  000e 0000
13            0000 d000  h000 0c00  0g00 00b0  00f0 000a  000e 0000
14 0000 d000  h000 0c00  0g00 00b0  00f0 000a  000e 0000
15 -------------------------------------------------------------------------------------------------
16 0000 d000  h000 dc00  hg00 dcb0  hgf0 dcba  hgfe dcba  hgfe 0cba  0gfe 00ba  00fe 000a  000e 0000
17 >> 32
18 -------------------------------------------------------------------------------------------------
19                                             0000 d000  h000 dc00  hg00 dcb0  hgf0 dcba  hgfe dcba  
20 &                                                                                       1111 1111
21 -------------------------------------------------------------------------------------------------
22                                                                                         hgfe dcba

請注意,在某些處理器上,最後兩步可以結合起來,因為暫存器可以作為位元組訪問;只需乘法,使一個暫存器儲存結果的上32位,取低位元組。因此,可能只需要6次操作。

用7個操作反轉一個位元組中的位(沒有64位)

 1 unsigned int v; // 32-bit word to reverse bit order
 2 
 3 // swap odd and even bits
 4 v = ((v >> 1) & 0x55555555) | ((v & 0x55555555) << 1);
 5 // swap consecutive pairs
 6 v = ((v >> 2) & 0x33333333) | ((v & 0x33333333) << 2);
 7 // swap nibbles ... 
 8 v = ((v >> 4) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 4);
 9 // swap bytes
10 v = ((v >> 8) & 0x00FF00FF) | ((v & 0x00FF00FF) << 8);
11 // swap 2-byte long pairs
12 v = ( v >> 16             ) | ( v               << 16);

下面的變化也是O(lg(N)),但是它需要更多的操作來反轉v,它的優點是通過在飛行中計算常數,佔用較少的記憶體。

unsigned int s = sizeof(v) * CHAR_BIT; // bit size; must be power of 2 
unsigned int mask = ~0;         
while ((s >>= 1) > 0) 
{
  mask ^= (mask << s);
  v = ((v >> s) & mask) | ((v << s) & ~mask);
}

以上這些方法最適合N大的情況。如果你在64位ints(或更大)的情況下使用上面的方法,那麼你需要增加更多的行數(按照模式),否則只有低32位會被反轉,結果會在低32位。

計算模數除以1<<s,不需要除法運算子

1 const unsigned int n;          // numerator
2 const unsigned int s;
3 const unsigned int d = 1U << s; // So d will be one of: 1, 2, 4, 8, 16, 32, ...
4 unsigned int m;                // m will be n % d
5 m = n & (d - 1); 

大多數程式設計師很早就學會了這一招,但為了完整起見,還是把它包括進去了。

計算模數除以(1<<s)-1,不含除法運算子

 1 unsigned int n;                      // numerator
 2 const unsigned int s;                // s > 0
 3 const unsigned int d = (1 << s) - 1; // so d is either 1, 3, 7, 15, 31, ...).
 4 unsigned int m;                      // n % d goes here.
 5 
 6 for (m = n; n > d; n = m)
 7 {
 8   for (m = 0; n; n >>= s)
 9   {
10     m += n & d;
11   }
12 }
13 // Now m is a value from 0 to d, but since with modulus division
14 // we want m to be 0 when it is d.
15 m = m == d ? 0 : m;

這種模數除以小於2次方的整數的方法最多需要5+(4+5*ceil(N/s)) * ceil(lg(N / s))操作,其中N是分子中的位數。換句話說,它最多需要O(N * lg(N))時間。

在沒有除法運算子的情況下,平行計算(1<<s)-1的模數除法

 1 // The following is for a word size of 32 bits!
 2 
 3 static const unsigned int M[] = 
 4 {
 5   0x00000000, 0x55555555, 0x33333333, 0xc71c71c7,  
 6   0x0f0f0f0f, 0xc1f07c1f, 0x3f03f03f, 0xf01fc07f, 
 7   0x00ff00ff, 0x07fc01ff, 0x3ff003ff, 0xffc007ff,
 8   0xff000fff, 0xfc001fff, 0xf0003fff, 0xc0007fff,
 9   0x0000ffff, 0x0001ffff, 0x0003ffff, 0x0007ffff, 
10   0x000fffff, 0x001fffff, 0x003fffff, 0x007fffff,
11   0x00ffffff, 0x01ffffff, 0x03ffffff, 0x07ffffff,
12   0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff
13 };
14 
15 static const unsigned int Q[][6] = 
16 {
17   { 0,  0,  0,  0,  0,  0}, {16,  8,  4,  2,  1,  1}, {16,  8,  4,  2,  2,  2},
18   {15,  6,  3,  3,  3,  3}, {16,  8,  4,  4,  4,  4}, {15,  5,  5,  5,  5,  5},
19   {12,  6,  6,  6 , 6,  6}, {14,  7,  7,  7,  7,  7}, {16,  8,  8,  8,  8,  8},
20   { 9,  9,  9,  9,  9,  9}, {10, 10, 10, 10, 10, 10}, {11, 11, 11, 11, 11, 11},
21   {12, 12, 12, 12, 12, 12}, {13, 13, 13, 13, 13, 13}, {14, 14, 14, 14, 14, 14},
22   {15, 15, 15, 15, 15, 15}, {16, 16, 16, 16, 16, 16}, {17, 17, 17, 17, 17, 17},
23   {18, 18, 18, 18, 18, 18}, {19, 19, 19, 19, 19, 19}, {20, 20, 20, 20, 20, 20},
24   {21, 21, 21, 21, 21, 21}, {22, 22, 22, 22, 22, 22}, {23, 23, 23, 23, 23, 23},
25   {24, 24, 24, 24, 24, 24}, {25, 25, 25, 25, 25, 25}, {26, 26, 26, 26, 26, 26},
26   {27, 27, 27, 27, 27, 27}, {28, 28, 28, 28, 28, 28}, {29, 29, 29, 29, 29, 29},
27   {30, 30, 30, 30, 30, 30}, {31, 31, 31, 31, 31, 31}
28 };
29 
30 static const unsigned int R[][6] = 
31 {
32   {0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000},
33   {0x0000ffff, 0x000000ff, 0x0000000f, 0x00000003, 0x00000001, 0x00000001},
34   {0x0000ffff, 0x000000ff, 0x0000000f, 0x00000003, 0x00000003, 0x00000003},
35   {0x00007fff, 0x0000003f, 0x00000007, 0x00000007, 0x00000007, 0x00000007},
36   {0x0000ffff, 0x000000ff, 0x0000000f, 0x0000000f, 0x0000000f, 0x0000000f},
37   {0x00007fff, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f},
38   {0x00000fff, 0x0000003f, 0x0000003f, 0x0000003f, 0x0000003f, 0x0000003f},
39   {0x00003fff, 0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f},
40   {0x0000ffff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff},
41   {0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff}, 
42   {0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff}, 
43   {0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff}, 
44   {0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff}, 
45   {0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff}, 
46   {0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff}, 
47   {0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff}, 
48   {0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff}, 
49   {0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff}, 
50   {0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff}, 
51   {0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff},
52   {0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff}, 
53   {0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff}, 
54   {0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff}, 
55   {0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff}, 
56   {0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff},
57   {0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff}, 
58   {0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff}, 
59   {0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff},
60   {0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff},
61   {0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff}, 
62   {0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff}, 
63   {0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff}
64 };
65 
66 unsigned int n;       // numerator
67 const unsigned int s; // s > 0
68 const unsigned int d = (1 << s) - 1; // so d is either 1, 3, 7, 15, 31, ...).
69 unsigned int m;       // n % d goes here.
70 
71 m = (n & M[s]) + ((n >> s) & M[s]);
72 
73 for (const unsigned int * q = &Q[s][0], * r = &R[s][0]; m > d; q++, r++)
74 {
75   m = (m >> *q) + (m & *r);
76 }
77 m = m == d ? 0 : m; // OR, less portably: m = m & -((signed)(m - d) >> s);

這種尋找小於2次冪的整數模數除法的方法最多需要O(lg(N))時間,其中N是分子中的位數(32位,上面的程式碼)。操作次數最多為12 + 9 * ceil(lg(N))。如果你在編譯時知道分母,就可以去掉表格;只需提取幾個相關的條目並展開迴圈。它可以很容易地擴充套件到更多的位。

它通過將基數(1<<s)中的值平行相加來計算結果。首先,每隔一個基數(1<<s)的值都要加到前一個值上。想象一下,結果是寫在一張紙上的。將紙切成兩半,這樣每張紙上就有一半的值。將這些值對齊,並將它們加到一張新的紙上。重複將這張紙切成兩半(將是前一張紙的四分之一大小),然後求和,直到不能再切下去。在進行lg(N/s/2)次切割後,我們就不再切割了,只需繼續將數值相加,並將結果放到一張新的紙上,就像之前一樣,同時至少有兩個s位數值。

用O(N)運算(顯而易見的方法)求一個整數的對數基數2與MSB N的集合

unsigned int v; // 32-bit word to find the log base 2 of
unsigned int r = 0; // r will be lg(v)

while (v >>= 1) // unroll for more speed...
{
  r++;
}

一個整數的對數基數2與最高位集(或最重要位集,MSB)的位置相同。以下的對數基數2方法比這個方法更快。

求一個64位IEEE浮點數的整數對數基數2

1 int v; // 32-bit integer to find the log base 2 of
2 int r; // result of log_2(v) goes here
3 union { unsigned int u[2]; double d; } t; // temp
4 
5 t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] = 0x43300000;
6 t.u[__FLOAT_WORD_ORDER!=LITTLE_ENDIAN] = v;
7 t.d -= 4503599627370496.0;
8 r = (t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] >> 20) - 0x3FF;

上面的程式碼將一個64位(IEEE-754浮點)的雙倍數與一個32位的整數(沒有墊位)一起載入,在指數設定為252的同時,將整數儲存在尾數中。從這個新造的雙數中減去252(用雙數表示),將所得指數設定為輸入值v的對數基數2,剩下的就是將指數位移到位置上(向右移20位),並減去偏置位0x3FF(即1023十進位制)。這種技術只需要5次操作,但是很多CPU在處理雙數時速度很慢,必須要照顧到架構的endianess。

用查詢表求一個整數的對數基數2

 1 static const char LogTable256[256] = 
 2 {
 3 #define LT(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n
 4     -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
 5     LT(4), LT(5), LT(5), LT(6), LT(6), LT(6), LT(6),
 6     LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7)
 7 };
 8 
 9 unsigned int v; // 32-bit word to find the log of
10 unsigned r;     // r will be lg(v)
11 register unsigned int t, tt; // temporaries
12 
13 if (tt = v >> 16)
14 {
15   r = (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt];
16 }
17 else 
18 {
19   r = (t = v >> 8) ? 8 + LogTable256[t] : LogTable256[v];
20 }

查表法只需要大約7次操作就可以找到一個32位值的對數。如果擴充套件到64位的量,大概需要9次操作。通過使用四個表,並將可能的加法納入到每個表中,可以減去另一個操作。使用int表元素可能更快,這取決於你的架構。

上面的程式碼被調整為均勻分佈的輸出值。如果你的輸入是均勻分佈在所有32位的值上,那麼可以考慮使用下面的程式碼:

 1 if (tt = v >> 24) 
 2 {
 3   r = 24 + LogTable256[tt];
 4 } 
 5 else if (tt = v >> 16) 
 6 {
 7   r = 16 + LogTable256[tt];
 8 } 
 9 else if (tt = v >> 8) 
10 {
11   r = 8 + LogTable256[tt];
12 } 
13 else 
14 {
15   r = LogTable256[v];
16 }

要通過演算法初步生成日誌表:

1 LogTable256[0] = LogTable256[1] = 0;
2 for (int i = 2; i < 256; i++) 
3 {
4   LogTable256[i] = 1 + LogTable256[i / 2];
5 }
6 LogTable256[0] = -1; // if you want log(0) to return -1

在O(lg(N))運算中求一個N位整數的對數基數2

 1 unsigned int v;  // 32-bit value to find the log2 of 
 2 const unsigned int b[] = {0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000};
 3 const unsigned int S[] = {1, 2, 4, 8, 16};
 4 int i;
 5 
 6 register unsigned int r = 0; // result of log2(v) will go here
 7 for (i = 4; i >= 0; i--) // unroll for speed...
 8 {
 9   if (v & b[i])
10   {
11     v >>= S[i];
12     r |= S[i];
13   } 
14 }
15 
16 
17 // OR (IF YOUR CPU BRANCHES SLOWLY):
18 
19 unsigned int v;             // 32-bit value to find the log2 of 
20 register unsigned int r; // result of log2(v) will go here
21 register unsigned int shift;
22 
23 r =     (v > 0xFFFF) << 4; v >>= r;
24 shift = (v > 0xFF  ) << 3; v >>= shift; r |= shift;
25 shift = (v > 0xF   ) << 2; v >>= shift; r |= shift;
26 shift = (v > 0x3   ) << 1; v >>= shift; r |= shift;
27                                         r |= (v >> 1);
28 
29 
30 // OR (IF YOU KNOW v IS A POWER OF 2):
31 
32 unsigned int v;  // 32-bit value to find the log2 of 
33 static const unsigned int b[] = {0xAAAAAAAA, 0xCCCCCCCC, 0xF0F0F0F0, 
34                                  0xFF00FF00, 0xFFFF0000};
35 register unsigned int r = (v & b[0]) != 0;
36 for (i = 4; i > 0; i--) // unroll for speed...
37 {
38   r |= ((v & b[i]) != 0) << i;
39 }

當然,如果要將程式碼擴充套件到查詢33位到64位數的對數,我們會將另一個元素0xFFFFFFFF00000000追加到b上,將32追加到S上,然後從5迴圈到0,這種方法比早期的查表版本要慢得多,但如果你不想要大表,或者你的架構對記憶體的訪問速度很慢,它是一個不錯的選擇。第二種變化涉及的操作略多,但在分支成本高的機器上可能更快(如PowerPC)。

用O(lg(N))運算求一個N位整數的對數基數2,並進行乘法和查詢

 1 uint32_t v; // find the log base 2 of 32-bit v
 2 int r;      // result goes here
 3 
 4 static const int MultiplyDeBruijnBitPosition[32] = 
 5 {
 6   0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
 7   8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
 8 };
 9 
10 v |= v >> 1; // first round down to one less than a power of 2 
11 v |= v >> 2;
12 v |= v >> 4;
13 v |= v >> 8;
14 v |= v >> 16;
15 
16 r = MultiplyDeBruijnBitPosition[(uint32_t)(v * 0x07C4ACDDU) >> 27];

上面的程式碼用小表查詢和乘法計算一個32位整數的對數基數2。它只需要13次操作,而前一種方法需要20次操作(最多)。純粹基於表的方法需要最少的操作,但這在表的大小和速度之間提供了一個合理的折中。

如果你知道v是2的冪,那麼你只需要以下幾點:

1 static const int MultiplyDeBruijnBitPosition2[32] = 
2 {
3   0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 
4   31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
5 };
6 r = MultiplyDeBruijnBitPosition2[(uint32_t)(v * 0x077CB531U) >> 27];

尋找整數對數基數10的整數

 1 unsigned int v; // non-zero 32-bit integer value to compute the log base 10 of 
 2 int r;          // result goes here
 3 int t;          // temporary
 4 
 5 static unsigned int const PowersOf10[] = 
 6     {1, 10, 100, 1000, 10000, 100000,
 7      1000000, 10000000, 100000000, 1000000000};
 8 
 9 t = (IntegerLogBase2(v) + 1) * 1233 >> 12; // (use a lg2 method from above)
10 r = t - (v < PowersOf10[t]);

整數對數基數10的計算方法是先用上面求對數基數2的技術之一。通過log10(v)=log2(v)/log2(10)的關係,我們需要將其乘以1/log2(10),大約是1233/4096,也就是1233後右移12。因為IntegerLogBase2四捨五入,所以需要加一。最後,由於值t只是一個近似值,可能會偏離1,所以要減去v<PowersOf10[t]的結果,才能找到準確的值。

這個方法比IntegerLogBase2多了6次操作。可以通過修改上面的對數基數2表查詢方法來加快速度(在記憶體訪問速度快的機器上),使條目保持為t計算的內容(即預新增、-mulitply和-shift)。這樣做總共只需要9次操作就可以找到對數基10,假設使用4個表(v的每個位元組一個表)。

尋找整數對數基數10的明顯方法

unsigned int v; // non-zero 32-bit integer value to compute the log base 10 of 
int r;          // result goes here

r = (v >= 1000000000) ? 9 : (v >= 100000000) ? 8 : (v >= 10000000) ? 7 : 
    (v >= 1000000) ? 6 : (v >= 100000) ? 5 : (v >= 10000) ? 4 : 
    (v >= 1000) ? 3 : (v >= 100) ? 2 : (v >= 10) ? 1 : 0;

當輸入均勻分佈在32位值上時,這種方法很好用,因為76%的輸入被第一次比較捕獲,21%的輸入被第二次比較捕獲,2%的輸入被第三次比較捕獲,以此類推(每次比較將剩餘的輸入砍掉90%)。因此,平均需要不到2.6次操作。

查詢32位IEEE浮點數的整數對數基數2

 1 const float v; // find int(log2(v)), where v > 0.0 && finite(v) && isnormal(v)
 2 int c;         // 32-bit int c gets the result;
 3 
 4 c = *(const int *) &v;  // OR, for portability:  memcpy(&c, &v, sizeof c);
 5 c = (c >> 23) - 127;
 6 The above is fast, but IEEE 754-compliant architectures utilize subnormal (also called denormal) floating point numbers. These have the exponent bits set to zero (signifying pow(2,-127)), and the mantissa is not normalized, so it contains leading zeros and thus the log2 must be computed from the mantissa. To accomodate for subnormal numbers, use the following:
 7 const float v;              // find int(log2(v)), where v > 0.0 && finite(v)
 8 int c;                      // 32-bit int c gets the result;
 9 int x = *(const int *) &v;  // OR, for portability:  memcpy(&x, &v, sizeof x);
10 
11 c = x >> 23;          
12 
13 if (c)
14 {
15   c -= 127;
16 }
17 else
18 { // subnormal, so recompute using mantissa: c = intlog2(x) - 149;
19   register unsigned int t; // temporary
20   // Note that LogTable256 was defined earlier
21   if (t = x >> 16)
22   {
23     c = LogTable256[t] - 133;
24   }
25   else
26   {
27     c = (t = x >> 8) ? LogTable256[t] - 141 : LogTable256[x] - 149;
28   }
29 }

求一個32位IEEE浮點數的pow(2, r)根的整數對數基2(對於無符號整數r)

1 const int r;
2 const float v; // find int(log2(pow((double) v, 1. / pow(2, r)))), 
3                // where isnormal(v) and v > 0
4 int c;         // 32-bit int c gets the result;
5 
6 c = *(const int *) &v;  // OR, for portability:  memcpy(&c, &v, sizeof c);
7 c = ((((c - 0x3f800000) >> r) + 0x3f800000) >> 23) - 127;

所以,例如,如果r為0,我們有c=int(log2((double)v))。如果r是1,那麼我們有c=int(log2(sqrt((double)v))。如果r是2,那麼我們有c = int(log2(pow((double) v, 1./4)))

以線性方式計算右側的連續零位(尾部)

 1 unsigned int v;  // input to count trailing zero bits
 2 int c;  // output: c will count v's trailing zero bits,
 3         // so if v is 1101000 (base 2), then c will be 3
 4 if (v)
 5 {
 6   v = (v ^ (v - 1)) >> 1;  // Set v's trailing 0s to 1s and zero rest
 7   for (c = 0; v; c++)
 8   {
 9     v >>= 1;
10   }
11 }
12 else
13 {
14   c = CHAR_BIT * sizeof(v);
15 }

(均勻分佈的)隨機二進位制數的平均尾部零位數是1,所以這個O(尾部零位)的解法與下面更快的方法相比並不差。

平行計算右邊的連續零位(尾部)

1 unsigned int v;      // 32-bit word input to count zero bits on right
2 unsigned int c = 32; // c will be the number of zero bits on the right
3 v &= -signed(v);
4 if (v) c--;
5 if (v & 0x0000FFFF) c -= 16;
6 if (v & 0x00FF00FF) c -= 8;
7 if (v & 0x0F0F0F0F) c -= 4;
8 if (v & 0x33333333) c -= 2;
9 if (v & 0x55555555) c -= 1;

在這裡,我們基本上是在並行地做著和找對數基數2一樣的操作,但我們先隔離出最低的1位,然後從最大的c開始遞減。大致上,對於N位字,操作次數最多為3*lg(N)+4。

通過二進位制搜尋計算右邊的連續零位(尾部)

 1 unsigned int v;     // 32-bit word input to count zero bits on right
 2 unsigned int c;     // c will be the number of zero bits on the right,
 3                     // so if v is 1101000 (base 2), then c will be 3
 4 // NOTE: if 0 == v, then c = 31.
 5 if (v & 0x1) 
 6 {
 7   // special case for odd v (assumed to happen half of the time)
 8   c = 0;
 9 }
10 else
11 {
12   c = 1;
13   if ((v & 0xffff) == 0) 
14   {  
15     v >>= 16;  
16     c += 16;
17   }
18   if ((v & 0xff) == 0) 
19   {  
20     v >>= 8;  
21     c += 8;
22   }
23   if ((v & 0xf) == 0) 
24   {  
25     v >>= 4;
26     c += 4;
27   }
28   if ((v & 0x3) == 0) 
29   {  
30     v >>= 2;
31     c += 2;
32   }
33   c -= v & 0x1;
34 }    

上面的程式碼與前面的方法類似,但它通過累加c的方式計算尾部零的數量,其方式類似於二進位制搜尋。在第一步中,它檢查v的底層16位是否為0,如果是,則將v右移16位,並將16加到c中,這樣v中要考慮的位數減少一半。隨後的每一個條件步驟同樣將位數減半,直到只有1位。這個方法比上一個方法快(大約33%),因為if語句的主體被執行的次數較少。

通過投擲到浮點數來計算右邊的連續零位(尾部)

1 unsigned int v;            // find the number of trailing zeros in v
2 int r;                     // the result goes here
3 float f = (float)(v & -v); // cast the least significant bit in v to a float
4 r = (*(uint32_t *)&f >> 23) - 0x7f;

雖然這隻需要大約6次操作,但在某些機器上,將整數轉換為浮點數的時間可能很高。將32位IEEE浮點表示法的指數下移,再減去偏置,得到v中設定的最不重要的1位的位置,如果v為零,那麼結果就是-127。

用模數除法和查詢法計算右邊的連續零位(尾數)

1 unsigned int v;  // find the number of trailing zeros in v
2 int r;           // put the result in r
3 static const int Mod37BitPosition[] = // map a bit value mod 37 to its position
4 {
5   32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4,
6   7, 17, 0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5,
7   20, 8, 19, 18
8 };
9 r = Mod37BitPosition[(-v & v) % 37];

上面的程式碼找到了右邊尾部的零的數量,所以二進位制0100會產生2。它利用了前32位位置值與37相對質數的事實,所以用37執行模數除法,每個位置值都有一個從0到36的唯一數字。然後,這些數字可以使用一個小的查詢表對映到0的數量。它只使用了4個操作,然而索引到表中並執行模數除法可能使它不適合某些情況。我獨立想出了這個辦法,然後搜尋表值的子序列,發現它是Reiser較早發明的,根據Hacker's Delight。

乘法和查詢來計算右邊的連續零位(尾部)

1 unsigned int v;  // find the number of trailing zeros in 32-bit v 
2 int r;           // result goes here
3 static const int MultiplyDeBruijnBitPosition[32] = 
4 {
5   0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 
6   31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
7 };
8 r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27];

用浮投法將其四捨五入到2的下一個最高倍數

 1 unsigned int const v; // Round this 32-bit value to the next highest power of 2
 2 unsigned int r;       // Put the result here. (So v=3 -> r=4; v=8 -> r=8)
 3 
 4 if (v > 1) 
 5 {
 6   float f = (float)v;
 7   unsigned int const t = 1U << ((*(unsigned int *)&f >> 23) - 0x7f);
 8   r = t << (t < v);
 9 }
10 else 
11 {
12   r = 1;
13 }
14 The code above uses 8 operations, but works on all v <= (1<<31).
15 Quick and dirty version, for domain of 1 < v < (1<<25):
16 
17 float f = (float)(v - 1);  
18 r = 1U << ((*(unsigned int*)(&f) >> 23) - 126);

雖然快速和骯髒的版本只使用了大約6個操作,但在Athlon™ XP 2100+ CPU上進行基準測試時,它比下面的技術(涉及12個操作)慢了大約三倍。不過,有些CPU的表現會更好。

四捨五入到2的下一個最高倍數

1 unsigned int v; // compute the next highest power of 2 of 32-bit v
2 
3 v--;
4 v |= v >> 1;
5 v |= v >> 2;
6 v |= v >> 4;
7 v |= v >> 8;
8 v |= v >> 16;
9 v++;

在12次操作中,這個程式碼計算一個32位整數的2的次高次冪。結果可以用公式1U << (lg(v - 1) + 1)來表示。請注意,在v為0的邊緣情況下,它返回的是0,這不是2的冪;如果重要的話,你可以附加表示式v += (v == 0)來補救。使用公式和使用查詢表的log base 2方法會快2個操作,但在某些情況下,查詢表並不適合,所以上述程式碼可能是最好的。(在Athlon™ XP 2100+上,我發現上述shift-left然後OR程式碼的速度與使用單條BSR組合語言指令的速度一樣快,該指令反向掃描以找到最高集位)。它的工作原理是將最高集位複製到所有的低位,然後加1,結果是將所有的低位都設定為0,最高集位以外的1位設定為1,如果原來的數字是2的冪,那麼減法就會將其減少1,這樣我們就可以四捨五入到相同的原值。