从unsigned int中提取位的函数

时间:2018-03-19 00:06:06

标签: c bit-manipulation bit bit-shift

  

编写一个名为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("%x\n", bitpat_get(x, 0, 3));
    printf("%x\n", 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

1 个答案:

答案 0 :(得分:3)

  

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

让我们先解决这个问题。假设您有一个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)会给出偏移量为3的五位,或defgh

这应该足以理解你需要做什么。

就您所需的内容而言,这是一个两步操作。第一个是实际将位移到(a),以便你需要的位于最右边的位置。这取决于三条信息:

  • unsigned int;
  • 的位宽
  • 要提取的偏移量;和
  • 您要提取的位数。

转移距离为bitWidth - offset - bitsNeeded。对于你的第一种情况,那将是16 - 0 - 3 = 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|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

第二步是屏蔽左边你实际上不需要的位。我们会先做第二个案例,因为它有实际效果。

掩码基本上是右边的一系列一位,可以通过从零开始获得, left 在一位中为您需要的每个位移位。对于我们需要五位的情况,序列将是二进制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的位宽。但是,您根据传入的值计算它,这是不正确的。你应该基于它的是一个位模式,其中最左边的位是一个。

换句话说,如果传入的值为3(二进制11),请考虑当前循环将执行的操作 - 位宽将计算为2,因为您最终会得到零值仅仅两班后。因此,更好的方法是:

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

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

第三,您应该知道^ xor 操作,如果您使用所有位的掩码,将反转位而不是按原样提取它们。您正在寻找的运营商是&

举例来说,使用带bitpat_get(21, 11, 5)的xor运算符会给出:

                     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}中的值。 1}}。

可能不是这样,所以我提供的代码涵盖了两种情况,如果你只是注释掉备用案例。