【问题标题】:Function that extracts bits from an unsigned int从无符号整数中提取位的函数
【发布时间】:2018-08-27 11:45:43
【问题描述】:

编写一个名为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

【问题讨论】:

  • 您编写的代码当然很重要。
  • @davernator 是的,它帮助我更好地理解位操作,即使我得出的结果被证明是错误的。
  • 非常好@Areg。此外,根据我的经验,一个好的基准(具有已知结果的实例)可以帮助您调试代码。如果代码在已知数据集上产生正确结果,则代码正确的可能性会大大增加。
  • @davernator 当然。但是,由于我不确定作者所说的提取一组指定的位是什么意思,所以我不太确定什么是好的基准。
  • 这可能发生,不是吗@Areg (:-)。如果你有作者的联系方式,也许你可以给他/她发电子邮件。一个很好的相关谷歌可能会查看移位运算符 > 的内容,以及它们如何在具有小端与大端架构的系统上运行。非常相关,但根据您想要进行的潜水深度,可能会过度杀伤力。 (:-)

标签: c bit-manipulation bit bit-shift


【解决方案1】:

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

让我们先解决这个问题。假设你有一个 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,您可以看到将位向右移动 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|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

第二步是屏蔽掉你实际上不需要的左边的位。我们先做第二种情况,因为它有实际效果。

掩码基本上是一系列右侧的一位,可以通过从零开始,向左为您需要的每个位位置移动一位来获得。对于我们需要 5 位的情况,序列将是二进制 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,因此不会影响原始值,因为所有最左边的位已经为零。 p>

请注意,您不需要在循环中执行此操作,因为正如您的代码所示,您可以使用单个表达式 2<sup>n</sup> - 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 操作,如果您使用全为一位的掩码,将 反转 位而不是按原样提取它们。你要找的接线员是&amp;

例如,将 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} 的值。

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

【讨论】:

  • 通常位的编号使得位 0 是最低有效位而不是最高有效位。这让事情变得更简单,因为您不需要知道无符号有多大——您只需按所需的位位置右移,函数就变成了 return (from &gt;&gt; start) &amp; ((1U &lt;&lt; n) - 1);
  • 好吧...所以我理解了你写的所有东西(这绝对有道理)并相应地改变了我的程序。但是,我运行了我的程序和你的程序,两种情况的输出都是 0。为什么会这样?我无法绕开它......
  • @ChrisDodd 那么作者提到的位编号以 MSB 开头的约定是不正确的?
  • 您可以以任何您想要的方式从数字的任一端提取,但是,传统的位编号是最低有效位为位0,最高有效位为位sizeof type * CHAR_BIT - 1 .想清楚这一点。如果您的值是1,您需要提取哪个位来提取值?只是1 只设置了一位。那么你是从 msb 中编号的类型吗?或者,您是否从 lsb 编号以便为您的值提取位?你可以随心所欲地做。这取决于您要检查的内容。
  • @Areg,检查bitWidth 的值。如果是 32,那么0x0000e1f4 的前 16 位都是零。使用0xe1f40000 快速检查会得到71 的正确值。
猜你喜欢
  • 1970-01-01
  • 2015-07-28
  • 2019-05-21
  • 1970-01-01
  • 2011-09-10
  • 2011-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多