有效地从 32 位数据类型中删除 2 位

Posted

技术标签:

【中文标题】有效地从 32 位数据类型中删除 2 位【英文标题】:Efficiently dropping 2 bits from a 32bit data type 【发布时间】: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。 【参考方案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 的额外信息。此解决方案独立于该条件工作。

以上是关于有效地从 32 位数据类型中删除 2 位的主要内容,如果未能解决你的问题,请参考以下文章

java的基本数据类型都有哪些

2017-09-11

Mysql中设置小数点用啥数据类型 decimal

C语言里,关于数据类型的大小问题

变量和基本类型C++

C++基础之数据类型和表达式