有效地从 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 << 1) & 0xFFFE0000) | ((number << 2) & 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 位的主要内容,如果未能解决你的问题,请参考以下文章