生成位掩码的算法

Alp*_*neo 43 algorithm bit-manipulation

我正面临着这个基于输入参数生成位掩码的独特问题.例如,

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

这是我在C中使用for循环实现的

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

    nMask |= (1 << i);
}
Run Code Online (Sandbox Code Playgroud)

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

Joh*_*zen 80

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

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

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

mask = (1 << param) - 1;
Run Code Online (Sandbox Code Playgroud)

编辑:

如果你想要一个> 32的param特例:

int sizeInBits = sizeof(mask) * BITS_PER_BYTE; // BITS_PER_BYTE = 8;
mask = (param >= sizeInBits ? -1 : (1 <<  param) - 1);
Run Code Online (Sandbox Code Playgroud)

此方法应适用于16位,32位或64位整数,但您可能必须显式键入"1".

  • 这是规范的解决方案,有两点需要注意.首先,您可能应该使用`unsigned int`作为"mask"和`1U`作为移位运算符的左侧,其次要注意,如果`param`等于或大于位数,则结果未指定在`int`中(或者如果继续使用带符号的数学运算,则小于位数).如果这是您环境中的问题,请改用查找表. (9认同)
  • 好主意,减去所有的:) (5认同)
  • 你有没有测试过它?在我的x86上,在gcc下,它产生零作为`param = 32`的掩码,而不是全部(因为x86移位实际上由param模32移位).在大多数情况下,我认为查找表不会明显变慢. (3认同)

Siu*_*ji- 19

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

C:

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

#define BIT_MASK(__TYPE__, __ONE_COUNT__) \
    ((__TYPE__) (-((__ONE_COUNT__) != 0))) \
    & (((__TYPE__) -1) >> ((sizeof(__TYPE__) * CHAR_BIT) - (__ONE_COUNT__)))
Run Code Online (Sandbox Code Playgroud)

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));
}
Run Code Online (Sandbox Code Playgroud)

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

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

BIT_MASK(uint64_t, 26) /* = 0x0000000003ffffffULL */
Run Code Online (Sandbox Code Playgroud)

#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;
}
Run Code Online (Sandbox Code Playgroud)

产量

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
Run Code Online (Sandbox Code Playgroud)

说明

首先,如在其他答案中已经讨论的那样,>>使用而不是<<为了在移位计数等于值的存储类型的位数时防止问题.(感谢朱利安上面的回答)

为了便于讨论,让我们用unsigned intas "实例化"宏,__TYPE__看看会发生什么(假设目前是32位):

((unsigned int) (-((__ONE_COUNT__) != 0))) \
& (((unsigned int) -1) >> ((sizeof(unsigned int) * CHAR_BIT) - (__ONE_COUNT__)))
Run Code Online (Sandbox Code Playgroud)

让我们关注:

((sizeof(unsigned int) * CHAR_BIT)
Run Code Online (Sandbox Code Playgroud)

第一.sizeof(unsigned int)在编译时已知.它等于4我们的假设.CHAR_BIT表示每个位的位数char,也就是每个字节的位数.它在编译时也是已知的.它等于8地球上的大多数机器.由于此表达式在编译时是已知的,因此编译器可能会在编译时执行乘法并将其视为常量,32在这种情况下等于.

让我们转到:

((unsigned int) -1)
Run Code Online (Sandbox Code Playgroud)

它等于0xFFFFFFFF.转换-1为任何无符号类型会在该类型中生成值"all-1s".这部分也是编译时常量.

到目前为止,表达式:

(((unsigned int) -1) >> ((sizeof(unsigned int) * CHAR_BIT) - (__ONE_COUNT__)))
Run Code Online (Sandbox Code Playgroud)

实际上与以下相同:

0xffffffffUL >> (32 - param)
Run Code Online (Sandbox Code Playgroud)

这和朱利安上面的回答是一样的.他的答案的一个问题是,如果param等于0,产生表达式0xffffffffUL >> 32,表达式的结果将是0xffffffffUL,而不是预期的0!(这就是为什么我将我的参数命名为__ONE_COUNT__强调其意图)

为了解决这个问题,我们可以简单地添加一个特殊的情况下__ONE_COUNT平等0使用if-else或者?:,就像这样:

#define BIT_MASK(__TYPE__, __ONE_COUNT__) \
    (((__ONE_COUNT__) != 0) \
    ? (((__TYPE__) -1) >> ((sizeof(__TYPE__) * CHAR_BIT) - (__ONE_COUNT__)))
    : 0)
Run Code Online (Sandbox Code Playgroud)

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

((unsigned int) (-((__ONE_COUNT__) != 0)))
Run Code Online (Sandbox Code Playgroud)

让我们从最里面的表达开始到最外面的表达.((__ONE_COUNT__) != 0)产生0当参数是0,或1以其他方式.(-((__ONE_COUNT__) != 0))产生0当参数是0,或-1以其他方式.因为((unsigned int) (-((__ONE_COUNT__) != 0))),((unsigned int) -1)上面已经解释了类型转换技巧.你现在注意到这个伎俩吗?表达方式:

((__TYPE__) (-((__ONE_COUNT__) != 0)))
Run Code Online (Sandbox Code Playgroud)

如果__ONE_COUNT__为零则等于"all-0s",否则等于"all-1s".它充当我们在第一步中计算的值的位掩码.因此,如果__ONE_COUNT__非零,那么掩码没有效果,它与Julien的答案相同.如果__ONE_COUNT__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
Run Code Online (Sandbox Code Playgroud)

  • 虽然这是一个很好的答案,但由于使用保留标识符,所编写的宏会调用未定义的行为。 (4认同)
  • 这是我曾经读过的最好的答案 (2认同)
  • 实际上,更糟糕的是,它还会在移位中调用未定义的行为(将32位无符号右移32将调用未定义的行为),因此,在解决标识符问题后,它甚至无法解决问题,因为它可能会崩溃或删除文件。 (2认同)
  • @SiuChingPong-AsukaKenji- 一个例子是它触发 CPU 异常,导致运行时跳转到删除文件的函数,然后该函数可以解释设置的任何寄存器以删除某些文件。确实,这可能不会发生在您的特定编译器和/或平台上,但问题是关于 C 语言,而不是它的特定实现。至于该声明的起源,请参阅任何版本的 C 标准并搜索“未定义的行为”。 (2认同)

Jul*_*yer 9

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

unsigned long const mask = 0xffffffffUL >> (32 - param);
Run Code Online (Sandbox Code Playgroud)

param <= 32当然,假设

  • 如果“long”是 32 位类型,则当 param = 0 时这不起作用 (2认同)

caf*_*caf 7

对于那些感兴趣的人来说,这是在另一个答案的评论中讨论的查找表替代方案 - 不同之处在于它对于32的参数正确工作.unsigned long long如果你需要,它很容易扩展到64位版本,并且应该速度有明显不同(如果在紧密的内循环中调用它,那么静态表将保持至少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 */
}
Run Code Online (Sandbox Code Playgroud)

值得指出的是,如果你需要这个if()声明(因为担心有人可能会把它称之为param > 32),那么这并没有赢得任何其他答案的替代方案:

unsigned long mask(unsigned param)
{
    if (param < 32)
        return (1UL << param) - 1;
    else
        return -1;
}
Run Code Online (Sandbox Code Playgroud)

唯一的区别是后者版本必须特殊情况param >= 32,而前者只有特殊情况param > 32.


归档时间:

查看次数:

45880 次

最近记录:

5 年,10 月 前