简体   繁体   English

了解如何在 C 中使用按位运算符计算数字的尾随零

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

Note - This is NOT a duplicate of this question - Count the consecutive zero bits (trailing) on the right in parallel: an explanation?注意- 这不是这个问题的重复 - 并行计算右侧的连续零位(尾随):解释? . . The linked question has a different context, it only asks the purpose of signed() being use.链接的问题有不同的上下文,它只询问使用signed()的目的。 DO NOT mark this question as duplicate.不要将此问题标记为重复。

I've been finding a way to acquire the number of trailing zeros in a number.我一直在寻找一种方法来获取数字中尾随零的数量。 I found a bit twiddling Stanford University Write up HERE here that gives the following explanation.我发现斯坦福大学有点乱写在这里,给出了以下解释。

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;

Why does this end up working ?为什么这最终会起作用? I have an understanding of how Hex numbers are represented as binary and bitwise operators, but I am unable to figure out the intuition behind this working ?我了解十六进制数如何表示为二进制和按位运算符,但我无法弄清楚这个工作背后的直觉? What is the working mechanism ?工作机制是什么?

The code is broken (undefined behavior is present).代码已损坏(存在未定义的行为)。 Here is a fixed version which is also slightly easier to understand (and probably faster):这是一个固定版本,它也更容易理解(并且可能更快):

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;

Once we know only one bit is set, we determine one bit of the result at a time, by simultaneously testing all bits where the result is odd, then all bits where the result has the 2's-place set, etc.一旦我们知道只有一位被设置,我们一次确定结果的一位,通过同时测试结果为奇数的所有位,然后结果设置为 2 的所有位,等等。

The original code worked in reverse, starting with all bits of the result set (after the if (c) c--; ) and then determining which needed to be zero and clearing them.原始代码反向工作,从结果集的所有位开始(在if (c) c--; ),然后确定哪些需要为零并清除它们。

Since we are learning one bit of the output at a time, I think it's more clear to build the output using bit operations not arithmetic.由于我们一次只学习一点输出,我认为使用位操作而不是算术来构建输出更清晰。

This code (from the net) is mostly C, although v &= -signed(v);这段代码(来自网络)主要是 C 语言,尽管v &= -signed(v); isn't correct C. The intent is for it to behave as v &= ~v + 1;不正确 C. 目的是让它表现为v &= ~v + 1;

First, if v is zero, then it remains zero after the & operation, and all of the if statements are skipped, so you get 32.首先,如果v为零,那么在&操作之后它仍然为零,并且所有的if语句都被跳过,所以你得到 32。

Otherwise, the & operation (when corrected) clears all bits to the left of the rightmost 1, so at that point v contains a single 1 bit.否则, &操作(修正后)会清除最右边 1 左边的所有位,因此此时v包含单个 1 位。 Then c is decremented to 31, ie all 1 bits within the possible result range.然后c递减到31,即可能结果范围内的所有1 位。

The if statements then determine its numeric position one bit at a time (one bit of the position number, not of v ), clearing the bits that should be 0.然后if语句一次一位确定其数字位置(位置编号的一位,而不是v一位),清除应为 0 的位。

The code first transforms v is such a way that is is entirely null, except the left most one that remains.代码首先转换 v 的方式是完全为空的,除了最左边的一个。 Then, it determines the position of this first one.然后,它确定第一个的位置。

First let's see how we suppress all ones but the left most one.首先让我们看看我们如何抑制除最左边的之外的所有。

Assume that k is the position of the left most one in v. v=(vn-1,vn-2,..vk+1,1,0,..0).假设k是v中最左边的位置。v=(vn-1,vn-2,..vk+1,1,0,..0)。

-v is the number that added to v will give 0 (actually it gives 2^n, but bit 2^n is ignored if we only keep the n less significant bits). -v 是添加到 v 将给出 0 的数字(实际上它给出 2^n,但如果我们只保留 n 个不太重要的位,则忽略位 2^n)。

What must the value of bits in -v so that v+-v=0? -v 中位的值必须是多少才能使 v+-v=0?

  • obviously bits k-1..0 of -k must be at 0 so that added to the trailing zeros in v they give a zero.显然 -k 的 k-1..0 位必须为 0,以便添加到 v 中的尾随零,它们给出零。

  • bit k must be at 1. Added to the one in vk, it will give a zero and a carry at one at order k+1位 k 必须为 1。添加到 vk 中的 1,它将以 k+1 的顺序给出 0 和 1 的进位

  • bit k+1 of -v will be added to vk+1 and to the carry generated at step k. -v 的第 k+1 位将被添加到 vk+1 和第 k 步生成的进位。 It must be the logical complement of vk+1.它必须是 vk+1 的逻辑补码。 So whatever the value of vk+1, we will have 1+0+1 if vk+1=0 (or 1+1+0 if vk+1=1) and result will be 0 at order k+1 with a carry generated at order k+2.因此,无论 vk+1 的值是多少,如果 vk+1=0(或如果 vk+1=1 则为 1+1+0),我们将有 1+0+1 并且结果将在 k+1 阶为 0 并带有进位在 k+2 阶生成。

  • This is similar for bits n-1..k+2 and they must all be the logical complement of the corresponding bit in v.这与 n-1..k+2 位类似,它们都必须是 v 中相应位的逻辑补码。

Hence, we get the well-known result that to get -v, one must因此,我们得到众所周知的结果,要得到 -v,必须

  • leave unchanged all trailing zeros of v保持 v 的所有尾随零不变

  • leave unchanged the left most one of v保持不变 v 中最左边的一个

  • complement all the other bits.补充所有其他位。

If we compute v&-v, we have如果我们计算 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

So v&-v only keeps the left most one in v.所以 v&-v 只保留 v 中最左边的一个。

To find the location of first one, look at the code:要找到第一个的位置,请查看代码:

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