【问题标题】:Efficiently dropping 2 bits from a 32bit data type有效地从 32 位数据类型中删除 2 位
【发布时间】:2018-12-21 08:08:02
【问题描述】:

假设你有一个 32 位的数据类型:

// The letters are just to identify the position of each bit later in the post
abcdefgh ijklmnop qrstuvwx yzABCDEF

我正在记录在某些位置“丢弃”位的最有效方法,其中丢弃意味着“删除”给定位,并移动以下位以填充其位置。

示例:假设我想删除位“a”和“q”。那么结果应该是这样的:

bcdefghi jklmnopr stuvwxyz ABCDEF00

00bcdefg hijklmno prstuvwx yzABCDEF

任何一个结果都可以接受。

在我的具体情况下,我还可以施加以下约束:

  • 在我的情况下,要删除的位置是静态的;也就是说,我总是需要准确地删除第 1 位和第 16 位(“a”和“q”)
  • 要删除的位(“a”和“q”)始终为 0
  • 最终填充数据的位(操作后向左或向右的“00”)无关紧要 - 即,它们实际上是 0 还是 1 无关紧要

目前我正在使用这样的方法(伪代码):

// called with number = abcdefgh ijklmnop qrstuvwx yzABCDEF
auto drop_bits_1_16(unsigned int number)
{
    number = number << 1; // number becomes: bcdefghi jklmnopq rstuvwxy zABCDEF0
    unsigned number1 = number & 0xFFFE0000;  // number1 comes: bcdefghi jklmnop0 00000000 00000000

    unsigned number2 = number & 0x0000FFFF; // number2 becomes: 00000000 00000000 rstuvwxy zABCDEF0
    number2 = number2 << 1;  // number2 becomes: 00000000 0000000r stuvwxyz ABCDEF00

    return number1 | number2;  // returns bcdefghi jklmnopr stuvwxyz ABCDEF00
}

但我想知道是否还有更聪明/更有效的方法?

【问题讨论】:

  • 如果有一个聪明的“位黑客”,它很有可能出现在Sean E. Anderson的编译中。
  • 如果你能忍受两边都是零,number + (unsigned short)number ;-)
  • 实际上,您将删除第 1 位和第 17 位,或 0 和 16 位,具体取决于您是基于 0 还是基于 1。

标签: c++ algorithm bit-manipulation


【解决方案1】:

向右打包比向左打包稍微容易一些,因为只需移动 15 位,而不是 15 的两倍。我不知道如何取消屏蔽,所以

((number & 0x7FFF0000) >> 1) | (number & 0x00007FFF)

这不需要丢弃的位为零。有四种按位运算,越少就越难。


三个操作都有办法!

将 15 个低位相加,将它们左移一位(乘以 2),然后将整体右移。

(number + (number & 0x7FFF)) >> 1

注意:第 15 位必须为零。

也许下面的表达式会给编译器一些更好的代码生成选项:

(number + (unsigned short)number) >> 1

我应该补充一下,其他最终布局也是可能的吗?

(number + (unsigned short)number) << 1

【讨论】:

  • 对不起,我不明白这是怎么回事。即使您将0xFFFF 替换为0xFFFF0000。请参阅我的答案的第二个 sn-p。
  • 好吧,我不得不说,我对最后一个表达印象深刻。显然,它将汇编指令的数量从 5 减少到 3。但不确定它有多快,因为指令不同。也不是很可读,但无论如何。不错。
  • 很好,这是一个非常聪明的解决方案.. 谢谢@YvesDaoust
【解决方案2】:

我想出了这个通用的解决方案。据我所知,需要有 3 个部分。

删除位 3 和 20 说。 (从零开始)

3
1            v                     v  0
hhhh hhhh hhhx mmmm mmmm mmmm mmmm xlll

您需要掩盖低点。中高部分,然后将它们挤压在一起。

template <size_t low, size_t hi> unsigned int remove_bits(unsigned int all)
{
    // static constants - my compiler pre-computes them.  They are the masks for
    // hhhh, mmmm and llll
    static const unsigned int lowMask = 0x7fffffff >> (31 - low);
    static const unsigned int middleMask = ((0xfffffffe << low) & (0x7fffffff >> (31 - hi) ));
    static const unsigned int highMask = 0xfffffffe << hi;

    // find the values in hhhh, mmmm, and llll
    unsigned int resLow = (all & lowMask);
    unsigned int resMiddle = (all & middleMask);
    unsigned int resHigh = (all & highMask);

    //////////////////////////////////////
    // combine the parts, shifted to the lower end.

    return resLow | resMiddle >> 1 | resHigh >> 2;
} 

使用类似的方式调用

printf("Question q %x\n", remove_bits<1, 31>(0x12345678));

【讨论】:

    【解决方案3】:

    你也可以换一种方式:

    auto drop_bits_1_16(unsigned int number)
    {
        unsigned number1 = number & 0x7FFF0000; // number1 becomes: 0bcdefgh ijklmnop 00000000 00000000
        unsigned number2 = number & 0x00007FFF; // number2 becomes: 00000000 00000000 0rstuvwx yzABCDEF
        number1 = number1 >> 1; // number1 becomes: 00bcdefg hijklmno p0000000 00000000
    
        return number1 | number2;  // returns 00bcdefg hijklmno prstuvwx yzABCDEF
    }
    

    这更短,并且具有更易读的优点:很清楚从位掩码中删除了哪些位。

    您也可以将其设为单线:

    auto drop_bits_1_16(unsigned int number)
    {
        return ((number & 0x7FFF0000) >> 1) | (number & 0x00007FFF);
        // Or, relying on operator precedence:
        // return (number & 0x7FFF0000) >> 1 | number & 0x00007FFF;
    }
    

    这可以说比你的解决方案变成单线更清楚:

    auto drop_bits_1_16(unsigned int number)
    {
        return ((number << 1) & 0xFFFE0000) | (((number << 1) & 0x0000FFFF) << 1);
        // Or, relying on operator precedence:
        // return number << 1 & 0xFFFE0000 | (number << 1 & 0x0000FFFF) << 1;
    }
    

    或者,正如@greybeard 所建议的(但仍然可以说不太清楚):

    auto drop_bits_1_16(unsigned int number)
    {
        return ((number << 1) & 0xFFFE0000) | ((number << 2) & 0x0001FFFC);
        // Or, relying on operator precedence:
        // return number << 1 & 0xFFFE0000 | number << 2 & 0x0001FFFC;
    }
    

    【讨论】:

    • (原码return ((number &lt;&lt; 1) &amp; 0xFFFE0000) | ((number &lt;&lt; 2) &amp; 0x0001FFFC);可以单行。)
    • @ruakh 是的,我想我的想法在那里跳过了一步。谢谢
    【解决方案4】:

    我认为没有什么比指定的实现更简单了:

    unsigned int drop_bits_16_32(unsigned int number)
    {
        number <<= 1;
        unsigned int high = number & 0xFFFE0000;
        unsigned int low = (number & 0x0000FFFF) << 1;
    
        return high | low;
    }
    

    【讨论】:

    • 那为什么还要重复呢?
    • 是的,有更简单的,你可以省去两个操作。
    【解决方案5】:

    您可以使用 4 条指令而不是 5 条指令来实现将位放在左侧的版本:

    unsigned f1(unsigned x) {
        x <<= 1;
        return x + ((signed) (x << 15) >> 15);
    }
    

    注意带符号的右移,它复制要删除的位,以便在添加中抵消。

    【讨论】:

    • 已经有四个甚至三个指令的解决方案(甚至是“两个半”)。
    • 据我所知,不适用于将位丢到左边的版本。
    • 不,两者都适用。阅读整篇文章。
    • 没错,我错过了要删除的位始终为 0 的额外信息。此解决方案独立于该条件工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-29
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 2014-05-15
    • 1970-01-01
    • 2020-04-03
    相关资源
    最近更新 更多