生成位掩码的算法

时间:2009-09-08 05:00:25

标签: algorithm bit-manipulation

我遇到了这个基于输入参数生成位掩码的独特问题。例如,

如果param = 2,则掩码为0x3(11b) 如果param = 5,则掩码将为0x1F(1 1111b)

这是我在C中使用for循环实现的,类似于

int nMask = 0;
for (int i = 0; i < param; i ++) {

    nMask |= (1 << i);
}

我想知道是否有更好的算法~~~

8 个答案:

答案 0 :(得分:72)

有关像这样的位掩码需要注意的一点是,它们总是小于2的幂。

表达式1 << n是获得2的n次幂的最简单方法。

您不希望Zero提供00000001的位掩码,您希望它提供零。所以你需要减去一个。

mask = (1 << param) - 1;

编辑:

如果你想要一个param的特殊情况&gt; 32:

int sizeInBits = sizeof(mask) * BITS_PER_BYTE; // BITS_PER_BYTE = 8;
mask = (param >= sizeInBits ? -1 : (1 <<  param) - 1);

此方法适用于16位,32位或64位整数,但您可能必须明确键入“1”。

答案 1 :(得分:16)

高效,无分支,可移植和通用(但丑陋)的实现

C:

#include <limits.h>     /* CHAR_BIT */

#define BIT_MASK(__TYPE__, __ONE_COUNT__) \
    ((__TYPE__) (-((__ONE_COUNT__) != 0))) \
    & (((__TYPE__) -1) >> ((sizeof(__TYPE__) * CHAR_BIT) - (__ONE_COUNT__)))

C ++:

#include <climits>

template <typename R>
static constexpr R bitmask(unsigned int const onecount)
{
//  return (onecount != 0)
//      ? (static_cast<R>(-1) >> ((sizeof(R) * CHAR_BIT) - onecount))
//      : 0;
    return static_cast<R>(-(onecount != 0))
        & (static_cast<R>(-1) >> ((sizeof(R) * CHAR_BIT) - onecount));
}

用法(生成编译时间常量)

BIT_MASK(unsigned int, 4) /* = 0x0000000f */

BIT_MASK(uint64_t, 26) /* = 0x0000000003ffffffULL */

实施例

#include <stdio.h>

int main()
{
    unsigned int param;
    for (param = 0; param <= 32; ++param)
    {
        printf("%u => 0x%08x\n", param, BIT_MASK(unsigned int, param));
    }
    return 0;
}

输出

0 => 0x00000000
1 => 0x00000001
2 => 0x00000003
3 => 0x00000007
4 => 0x0000000f
5 => 0x0000001f
6 => 0x0000003f
7 => 0x0000007f
8 => 0x000000ff
9 => 0x000001ff
10 => 0x000003ff
11 => 0x000007ff
12 => 0x00000fff
13 => 0x00001fff
14 => 0x00003fff
15 => 0x00007fff
16 => 0x0000ffff
17 => 0x0001ffff
18 => 0x0003ffff
19 => 0x0007ffff
20 => 0x000fffff
21 => 0x001fffff
22 => 0x003fffff
23 => 0x007fffff
24 => 0x00ffffff
25 => 0x01ffffff
26 => 0x03ffffff
27 => 0x07ffffff
28 => 0x0fffffff
29 => 0x1fffffff
30 => 0x3fffffff
31 => 0x7fffffff
32 => 0xffffffff

解释

首先,正如在其他答案中已经讨论的那样,使用>>代替<<,以便在移位计数等于存储类型的位数时防止出现问题价值。 (感谢Julien's answer above的想法)

为了便于讨论,让我们将unsigned int __TYPE__“实例化”为((unsigned int) (-((__ONE_COUNT__) != 0))) \ & (((unsigned int) -1) >> ((sizeof(unsigned int) * CHAR_BIT) - (__ONE_COUNT__))) ,看看会发生什么(假设目前是32位):

((sizeof(unsigned int) * CHAR_BIT)

让我们关注:

sizeof(unsigned int)

第一。 4在编译时是已知的。根据我们的假设,它等于CHAR_BITchar表示每个字节的每8个比特数,a.k.a。它在编译时也是已知的。它在地球上的大多数机器上等于32。由于这个表达式在编译时是已知的,编译器可能会在编译时进行乘法并将其视为常量,在这种情况下等于((unsigned int) -1)

让我们转到:

0xFFFFFFFF

等于-1。将(((unsigned int) -1) >> ((sizeof(unsigned int) * CHAR_BIT) - (__ONE_COUNT__))) 强制转换为任何无符号类型会在该类型中生成值“all-1s”。这部分也是编译时常量。

到目前为止,表达式为:

0xffffffffUL >> (32 - param)

实际上与:

相同
param

与朱利安上面的回答相同。他回答的一个问题是,如果0等于0xffffffffUL >> 32,则生成表达式0xffffffffUL,表达式的结果将是0,而不是预期的__ONE_COUNT__ 1}}! (这就是为什么我将我的参数命名为__ONE_COUNT以强调其意图)

要解决此问题,我们只需使用0if-else?:等于#define BIT_MASK(__TYPE__, __ONE_COUNT__) \ (((__ONE_COUNT__) != 0) \ ? (((__TYPE__) -1) >> ((sizeof(__TYPE__) * CHAR_BIT) - (__ONE_COUNT__))) : 0) 添加一个特殊情况,如下所示:

((unsigned int) (-((__ONE_COUNT__) != 0)))

但是无分支代码更酷,不是吗?!让我们转到下一部分:

((__ONE_COUNT__) != 0)

让我们从最里面的表达开始到最外面的表达。参数为0时,0会生成1,否则会生成(-((__ONE_COUNT__) != 0))。参数为0时,0会生成-1,否则会生成((unsigned int) (-((__ONE_COUNT__) != 0)))。对于((unsigned int) -1),上面已经解释了类型转换技巧((__TYPE__) (-((__ONE_COUNT__) != 0))) 。你现在注意到这个伎俩吗?表达式:

__ONE_COUNT__
如果__ONE_COUNT__为零,则

等于“all-0s”,否则为“all-1s”。它充当我们在第一步中计算的值的位掩码。因此,如果__ONE_COUNT__非零,则掩码无效,与Julien的答案相同。如果0__ONE_COUNT__ : 0 Other ------------- -------------- (__ONE_COUNT__) 0 = 0x000...0 (itself) ((__ONE_COUNT__) != 0) 0 = 0x000...0 1 = 0x000...1 ((__TYPE__) (-((__ONE_COUNT__) != 0))) 0 = 0x000...0 -1 = 0xFFF...F ,它会掩盖朱利安答案的所有位,产生一个恒定的零。要想象,请观看:

{{1}}

答案 2 :(得分:8)

或者,您可以使用右移来避免(1 << param) - 1解决方案中提到的问题。

unsigned long const mask = 0xffffffffUL >> (32 - param);

当然假设param <= 32

答案 3 :(得分:6)

对于那些感兴趣的人,这是在对另一个答案的评论中讨论的查找表替代方案 - 区别在于它对于32的参数正确工作。很容易扩展到64位unsigned long long版本,如果你需要它,并且不应该在速度上有显着差异(如果它在一个紧密的内部循环中被调用,那么静态表将至少保留在L2缓存中,并且如果它是不是在一个紧密的内环然后性能差异并不重要。)

unsigned long mask2(unsigned param)
{
    static const unsigned long masks[] = {
        0x00000000UL, 0x00000001UL, 0x00000003UL, 0x00000007UL,
        0x0000000fUL, 0x0000001fUL, 0x0000003fUL, 0x0000007fUL,
        0x000000ffUL, 0x000001ffUL, 0x000003ffUL, 0x000007ffUL,
        0x00000fffUL, 0x00001fffUL, 0x00003fffUL, 0x00007fffUL,
        0x0000ffffUL, 0x0001ffffUL, 0x0003ffffUL, 0x0007ffffUL,
        0x000fffffUL, 0x001fffffUL, 0x003fffffUL, 0x007fffffUL,
        0x00ffffffUL, 0x01ffffffUL, 0x03ffffffUL, 0x07ffffffUL,
        0x0fffffffUL, 0x1fffffffUL, 0x3fffffffUL, 0x7fffffffUL,
        0xffffffffUL };

    if (param < (sizeof masks / sizeof masks[0]))
        return masks[param];
    else
        return 0xffffffffUL; /* Or whatever else you want to do in this error case */
}

值得指出的是,如果你需要if()声明(因为担心某人可能会用param > 32来称呼它),那么这并不能从其他答案中获得任何替代品:

unsigned long mask(unsigned param)
{
    if (param < 32)
        return (1UL << param) - 1;
    else
        return -1;
}

唯一的区别是后一版本需要特殊情况param >= 32,而前一版本只需要特殊情况param > 32

答案 4 :(得分:1)

这个(用Java):

int mask = -1;
mask = mask << param;
mask = ~mask;

这样就可以避免查找表和硬编码整数的长度。

说明:值为-1的有符号整数用二进制表示为全1。向左移动给定的次数,将多个0添加到右侧。这将导致各种“反向掩码”。然后否定移位的结果来创建你的面具。

这可以缩短为:

int mask = ~(-1<<param);

一个例子:

int param = 5;
int mask = -1;        // 11111111 (shortened for example)
mask = mask << param; // 11100000
mask = ~mask;         // 00011111

答案 5 :(得分:0)

如果您担心使用(1 << param) - 1的类C语言的溢出(当参数在最大大小类型为32或64时,由于位移位超出了类型的界限,则掩码变为0),一种解决方案我只是想到了:

const uint32_t mask = ( 1ul << ( maxBits - 1ul ) ) | ( ( 1ul << ( maxBits - 1ul ) ) - 1ul );

或者另一个例子

const uint64_t mask = ( 1ull << ( maxBits - 1ull ) ) | ( ( 1ull << ( maxBits - 1ull ) ) - 1ull );

这是一个模板化版本,请记住,您应该将其与无符号类型R一起使用:

#include <limits.h>     /* CHAR_BIT */

// bits cannot be 0
template <typename R>
static constexpr R bitmask1( const R bits )
{
    const R one = 1;
    assert( bits >= one );
    assert( bits <= sizeof( R ) * CHAR_BIT );
    const R bitShift = one << ( bits - one );
    return bitShift | ( bitShift - one );
}

比方说,最大位是8个字节,在第一个溢出函数中,我们有1 << 8 == 256,当转换为字节时,它变为0。在我的函数中,我们有1 << 7 == 128,这是一个字节可以包含,因此变成1<<7 | 1<<7 - 1

我尚未编译该函数,因此它可能包含错别字。


为了娱乐,这里Julien Royer充实了:

// bits can be 0
template <typename R>
static constexpr R bitmask2( const R bits )
{
    const R zero = 0;
    const R mask = ~zero;
    const R maxBits = sizeof( R ) * CHAR_BIT;
    assert( bits <= maxBits );
    return mask >> ( maxBits - bits );
}

答案 6 :(得分:0)

从我的头顶上。抱歉,我正在使用手机。为了清楚起见,我假定使用64位类型,但这可以很容易地概括。

(((uint64_t) (bits < 64)) << (bits & 63)) - 1u

这是典型的(1 << bits) - 1但无分支,没有未定义的行为,& 63在某些平台上可以优化,并且在整个值范围内都具有正确的结果。

对于大于或等于类型宽度的移位,左(左)移位操作数变为0。

屏蔽右(左)移位操作数以避免未定义的行为,该值永远不会大于63。这只是使编译器和语言律师满意,因为当左操作数已经存在时,没有平台会添加零(对于大于63的值)。一个好的编译器应该在已经是基础指令行为的平台(例如x86)上删除& 63掩码。

我们已经看到,大于63的值从移位中得到0的结果,但是之后相减1,则所有位都由无符号整数下溢设置,这在无符号类型上并不是未定义的行为。

答案 7 :(得分:-1)

仅供参考(谷歌),我使用以下代码为整数类型获取了全1掩码。
在C ++中,可能只是使用:

std::numeric_limits<uint_16t>::max() // 65535