[英]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.