我遇到了一个独特的问题,需要根据输入参数生成位掩码。
例如,如果参数为2,则掩码将为0x3(11b);如果参数为5,则掩码将为0x1F(11111b)。
我使用C中的for循环来实现这个功能,大致如下:
int nMask = 0;
for (int i = 0; i < param; i ++) {
nMask |= (1 << i);
}
我想知道是否有更好的算法 ~~~
我遇到了一个独特的问题,需要根据输入参数生成位掩码。
例如,如果参数为2,则掩码将为0x3(11b);如果参数为5,则掩码将为0x1F(11111b)。
我使用C中的for循环来实现这个功能,大致如下:
int nMask = 0;
for (int i = 0; i < param; i ++) {
nMask |= (1 << i);
}
我想知道是否有更好的算法 ~~~
需要注意的是,这样的位掩码始终比2的幂少1个。
表达式1 << n
是获得第n个二次幂最简单的方法。
你不希望零提供00000001
的位掩码,而是希望它提供0。因此,你需要减去一。
mask = (1 << param) - 1;
编辑:
如果您需要针对参数大于32的特殊情况:
int sizeInBits = sizeof(mask) * BITS_PER_BYTE; // BITS_PER_BYTE = 8;
mask = (param >= sizeInBits ? -1 : (1 << param) - 1);
这种方法适用于16、32或64位整数,但您可能需要显式地为'1'指定类型。
unsigned int
作为mask
的类型,并将1U
用作移位运算符的左侧;其次,请注意如果param
等于或大于int
中的位数(如果您继续使用带符号数学,则为比位数少一),则结果是未指定的。如果在您的环境中存在此问题,请改用查找表。 - cafparam == width_of_unsigned_in_bits
的情况会产生未定义行为,但实际上很难遇到不会在这种情况下返回0的实现。因此,在实践中,我不会特别关注这个if
特殊情况,因为主要代码可以很好地处理它。 - j_random_hackerparam=32
时,它生成的掩码为0,而不是全1(因为x86移位实际上按照参数模32进行移位)。我认为在大多数情况下,查找表的速度不会显著变慢。 - cafC:
#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的答案提供的思路)
为了方便讨论,让我们把宏实例化为unsigned int
,并看看会发生什么(假设现在是32位):
((unsigned int) (-((__ONE_COUNT__) != 0))) \
& (((unsigned int) -1) >> ((sizeof(unsigned int) * CHAR_BIT) - (__ONE_COUNT__)))
让我们聚焦于:
((sizeof(unsigned int) * CHAR_BIT)
首先,sizeof(unsigned int)
在编译时是已知的。根据假设它等于4
。 CHAR_BIT
表示每个字节中位数的数量,也在编译时已知。在地球上的大多数计算机上,它等于8
。由于这个表达式在编译时是已知的,编译器可能会在编译时进行乘法运算并将其视为一个常数,在本例中等于32
。
接下来让我们转到:
((unsigned int) -1)
它等于0xFFFFFFFF
。将-1
转换为任何无符号类型都会产生该类型中的“全1”值。这部分也是编译时常量。
到目前为止,表达式:
(((unsigned int) -1) >> ((sizeof(unsigned int) * CHAR_BIT) - (__ONE_COUNT__)))
实际上与以下内容相同:
0xffffffffUL >> (32 - param)
这与上面Julien的答案相同。他的答案有一个问题,如果param
等于0
,那么产生的表达式0xffffffffUL >> 32
的结果将是0xffffffffUL
,而不是预期的0
!(这就是为什么我将我的参数命名为__ONE_COUNT__
以强调其意图)
要解决这个问题,我们可以简单地添加一个特殊情况来处理__ONE_COUNT__
等于0
,使用if-else
或?:
,像这样:
这与Julien的答案相同。他的答案有一个问题,如果param
等于0
,那么产生的表达式0xffffffffUL >> 32
的结果将是0xffffffffUL
,而不是预期的0
!(这就是为什么我将我的参数命名为__ONE_COUNT__
以强调其意图)
为了解决这个问题,我们可以通过使用if-else
或?:
语句,在代码中添加一个特殊情况来处理__ONE_COUNT__
等于0
的情况,像这样:
#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__
为零,则等于“全0”,否则为“全1”,它作为我们在第一步计算的值的位掩码。因此,如果__ONE_COUNT__
非零,则该掩码没有影响,与Julien的答案相同。如果__ONE_COUNT__
是0
,则屏蔽掉Julien答案的所有位,产生一个常量零。可视化效果如下:
__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 << param) - 1
解决方案中提到的问题。
unsigned long const mask = 0xffffffffUL >> (32 - param);
假设当然前提是 param <= 32
。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
。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
ull
)才能使其对于(几乎)任何类型都有效:#define BITMASK_GEN(pos, len) (~(~0ull << len) << pos)
。这适用于除unsigned __int128
之外的所有类型。 - alx - recommends codidact从我的经验来看,抱歉我在移动设备上。为了清晰起见,我假设使用64位类型,但这可以很容易地推广。
(((uint64_t) (bits < 64)) << (bits & 63)) - 1u
(1 << bits) - 1
,对于整个值范围都能得到正确结果,在某些平台上& 63
可以被优化掉。& 63
掩码处理。(1 << param) - 1
(当param为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 );
}
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 );
}
如果您需要一个32位掩码,可以使用以下代码(对于64位掩码,请使用uint64_t
):
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int
main()
{
size_t n = 8;
assert(n <= 32);
uint32_t mask = ~(uint32_t)0 >> (32 - n);
printf("mask = %08" PRIX32 "\n", mask);
}
我知道这是对一个非常旧的帖子的回答。但是如果有人类真正阅读了这个:我欢迎任何反馈。
32
,这里有一个适用于所有无符号类型和所有值从 1
到类型宽度的解决方案:uint32_t mask = -1; mask = ~(mask << (n - 1) << 1);
- chqrlie-1
表示为有符号整数,在转换为无符号类型时,值都是该类型的最大值,并且 uint32_t
必须恰好有 32 位。 - chqrlie仅供参考(谷歌),我使用以下内容获取整数类型的所有1
掩码。
在C++中,可以简单地使用:
std::numeric_limits<uint_16t>::max() // 65535
1
位的掩码,而不是如何获取全部为 1。 - phuclv