c - 从无符号整数中提取位的函数



>编写一个名为bitpat_get()的函数来提取一组指定的位。它有三个参数:第一个是unsigned int,第二个是整数起始位号,第三个是位计数。使用位编号从最左侧位的0开始的约定,从第一个参数中提取指定的位数并返回结果。所以电话

bitpat_get(x, 0, 3)

从中提取最左边的三个位。电话会议

bitpat_get(x, 3, 5)

从左起第四位开始提取五个位。

我真的不知道作者提取位是什么意思,所以我几乎可以肯定我的代码是错误的,无论它返回什么都不是预期的返回值。但是,我还是会发布它:

#include <stdio.h>
unsigned int bitpat_get(unsigned int from, int start, int n);
int main(void)
{
unsigned int x = 0xe1f4;
printf("%xn", bitpat_get(x, 0, 3));
printf("%xn", bitpat_get(x, 3, 5));
}
unsigned int bitpat_get(unsigned int from, int start, int n)
{
unsigned int result = from;
int bits;
for (bits = 0; (from >> bits) != 0; ++bits)
continue;
unsigned int mask = (((1U << n) - 1) << (bits - n - start));
result = from ^ mask;
return result;
}

输出:

1f4
fef4

我真的不知道作者提取位是什么意思。

让我们先解决这个问题。假设您有一个 16 位无符号整数,位位置为:

1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

因此,表达式bitpat_get(x, 0, 3)应该为您提供从偏移量零开始的三位,即abc。同样,bitpat_get(x, 3, 5)会给你偏移量三的五位,或defgh

这应该足以了解您需要做什么。


就您需要做什么来实现这一点而言,这是一个两步操作。首先是实际向右移动位(a),以便您需要的位位于最右侧的位置。这取决于三条信息:

  • unsigned int的位宽;
  • 要提取的偏移量;以及
  • 要提取的位数。

换档距离是bitWidth - offset - bitsNeeded.对于您的第一种情况,这将是16 - 0 - 3 = 13,您可以看到将位向右移动 13 会将所需的位放在最右侧的部分:

1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|0|0|0|0|0|a|b|c|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

对于第二种情况,按16 - 3 - 5 = 8右移可为您提供:

1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|a|b|c|d|e|f|g|h|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

第二步是屏蔽左边你实际上不需要的部分。我们将首先处理第二种情况,因为这具有实际效果。

掩码基本上是右侧的一系列一位,可以通过从零开始获得,并且对于您需要的每个位位置,在一位中左移。对于我们需要五个位的情况,序列将是二进制0111111111111111。按位和与值相提并论将得到:

1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|a|b|c|d|e|f|g|h| <- value
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|0|0|0|1|1|1|1|1| <- "and" with
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|0|0|0|d|e|f|g|h| <- gives
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

对于我们需要三位的第一种情况,掩码将是二进制111因此对原始值没有影响,因为所有最左边的位都已经为零。

请注意,您不需要在循环中执行此操作,因为正如您的代码所示,您可以使用单个表达式2n- 1计算它:

unsigned mask = (1U << n) - 1U;

就您发布的代码而言,我看到了一些问题。

首先,我认为您的for..continue部分旨在根据您后来对该值的使用来找出unsigned int的位宽。但是,您根据传入的值计算它,这是不正确的。您应该基于一个位模式,其中最左边的位是一个。

换句话说,想想如果你传入的值是三(二进制11),你的电流循环会做什么 - 位宽将计算为二,因为你最终会在两个班次后得到一个零值。因此,更好的方法是:

unsigned testVal = ~0U; // all one bits
for (bits = 0; testVal != 0; ++bits, testVal = testVal >> 1)
;

其次是你的掩码计算。您的代码设置为就地提取位,这意味着您只需将所有其他位设置为零。最好将它们移到右侧以进行提取(a)。

第三,您应该知道^异或操作,如果您使用所有一位的掩码,它将反转位而不是按原样提取它们。您要查找的运算符是&

例如,将 xor 运算符与bitpat_get(21, 11, 5)一起使用将得到:

1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|0|0|0|1|0|1|0|1| <- value (21)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|0|0|0|1|1|1|1|1| <- "xor" with
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|0|0|0|0|1|0|1|0| <- `01010` (10): NOT the correct `10101`
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

说了这么多,我会把函数写成这样:

unsigned bitpat_get(unsigned from, unsigned start, unsigned count) {
// Only need calculate this once, first time it's called.
static unsigned bitWidth = 0;
if (bitWidth == 0) {
unsigned testVal = ~0U;
while (testVal != 0) {
bitWidth++;
testVal = testVal >> 1;
}
}
// Get the value you need to shift by.
unsigned shiftCount = bitWidth - start - count;
// Use this line if in-place bits needed.
// unsigned mask = ((1U << count) - 1U) << shiftCount;
// Or use these two lines if you need it on the right.
from = from >> shiftCount;
unsigned mask = (1U << count) - 1U;
// Mask and return the bits.
unsigned result = from & mask;
return result;
}

唯一棘手的一点是使用静态bitWidth因此只需要计算一次。这只是一种优化,以加快后续调用的速度。如果您不希望这样做(例如,如果您对这些概念不满意,或者如果可能第一次从多个线程并发调用此函数,从而导致数据竞争),只需将其替换为:

unsigned bitWidth = 0;
unsigned testVal = ~0U;
while (testVal != 0) {
bitWidth++;
testVal = testVal >> 1;
}

(a)这是根据经验。您可能希望它们就位,但是,在我漫长而(偶尔)辉煌的职业生涯中,我一直发现将它们放在移位部分更有用。例如,如果位 11-13 是某种整数值,将它们移动到最右侧的位实际上会给你0..7,而不是集合{0, 4, 8, ..., 28}中的值。

情况可能并非如此,因此如果您只是注释掉备用情况,我提供的代码涵盖了这两种情况。

最新更新