簡體   English   中英

了解如何在 C 中使用按位運算符計算數字的尾隨零

[英]UNDERSTANDING how to count trailing zeros for a number using bitwise operators in C

注意- 這不是這個問題的重復 - 並行計算右側的連續零位(尾隨):解釋? . 鏈接的問題有不同的上下文,它只詢問使用signed()的目的。 不要將此問題標記為重復。

我一直在尋找一種方法來獲取數字中尾隨零的數量。 我發現斯坦福大學有點亂寫在這里,給出了以下解釋。

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

為什么這最終會起作用? 我了解十六進制數如何表示為二進制和按位運算符,但我無法弄清楚這個工作背后的直覺? 工作機制是什么?

代碼已損壞(存在未定義的行為)。 這是一個固定版本,它也更容易理解(並且可能更快):

uint32_t v;  // 32-bit word input to count zero bits on right
unsigned c;  // c will be the number of zero bits on the right
if (v) {
    v &= -v; // keep rightmost set bit (the one that determines the answer) clear all others
    c = 0;
    if (v & 0xAAAAAAAAu) c |= 1; // binary 10..1010
    if (v & 0xCCCCCCCCu) c |= 2; // binary 1100..11001100
    if (v & 0xF0F0F0F0u) c |= 4;
    if (v & 0xFF00FF00u) c |= 8;
    if (v & 0xFFFF0000u) c |= 16;
}
else c = 32;

一旦我們知道只有一位被設置,我們一次確定結果的一位,通過同時測試結果為奇數的所有位,然后結果設置為 2 的所有位,等等。

原始代碼反向工作,從結果集的所有位開始(在if (c) c--; ),然后確定哪些需要為零並清除它們。

由於我們一次只學習一點輸出,我認為使用位操作而不是算術來構建輸出更清晰。

這段代碼(來自網絡)主要是 C 語言,盡管v &= -signed(v); 不正確 C. 目的是讓它表現為v &= ~v + 1;

首先,如果v為零,那么在&操作之后它仍然為零,並且所有的if語句都被跳過,所以你得到 32。

否則, &操作(修正后)會清除最右邊 1 左邊的所有位,因此此時v包含單個 1 位。 然后c遞減到31,即可能結果范圍內的所有1 位。

然后if語句一次一位確定其數字位置(位置編號的一位,而不是v一位),清除應為 0 的位。

代碼首先轉換 v 的方式是完全為空的,除了最左邊的一個。 然后,它確定第一個的位置。

首先讓我們看看我們如何抑制除最左邊的之外的所有。

假設k是v中最左邊的位置。v=(vn-1,vn-2,..vk+1,1,0,..0)。

-v 是添加到 v 將給出 0 的數字(實際上它給出 2^n,但如果我們只保留 n 個不太重要的位,則忽略位 2^n)。

-v 中位的值必須是多少才能使 v+-v=0?

  • 顯然 -k 的 k-1..0 位必須為 0,以便添加到 v 中的尾隨零,它們給出零。

  • 位 k 必須為 1。添加到 vk 中的 1,它將以 k+1 的順序給出 0 和 1 的進位

  • -v 的第 k+1 位將被添加到 vk+1 和第 k 步生成的進位。 它必須是 vk+1 的邏輯補碼。 因此,無論 vk+1 的值是多少,如果 vk+1=0(或如果 vk+1=1 則為 1+1+0),我們將有 1+0+1 並且結果將在 k+1 階為 0 並帶有進位在 k+2 階生成。

  • 這與 n-1..k+2 位類似,它們都必須是 v 中相應位的邏輯補碼。

因此,我們得到眾所周知的結果,要得到 -v,必須

  • 保持 v 的所有尾隨零不變

  • 保持不變 v 中最左邊的一個

  • 補充所有其他位。

如果我們計算 v&-v,我們有

 v      vn-1  vn-2    ...  vk+1  1   0   0  ... 0
-v   & ~vn-1 ~vn-2    ... ~vk+1  1   0   0  ... 0
v&-v      0     0     ...    0   1   0   0  ... 0

所以 v&-v 只保留 v 中最左邊的一個。

要找到第一個的位置,請查看代碼:

if (v) c--;  // no 1 in result? -> 32 trailing zeros. 
             // Otherwise it will be in range c..0=31..0
if (v & 0x0000FFFF) c -= 16; // If there is a one in left most part of v  the range
                             //   of possible values for the location of this one
                             //   will be 15..0.
                             // Otherwise, range must 31..16
                             // remaining range is c..c-15 
if (v & 0x00FF00FF) c -= 8;  // if there is one in either byte 0 (c=15) or byte 2 (c=31), 
                             // the one is in the lower part of range.
                             // So we must substract 8 to boundaries of range.
                             // Other wise, the one is in the upper part.
                             // Possible range of positions of v is now c..c-7
if (v & 0x0F0F0F0F) c -= 4;  // do the same for the other bits.
if (v & 0x33333333) c -= 2;
if (v & 0x55555555) c -= 1;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM