我想获得一个函数,将数字类型的n
最后一位设置为1
。例如:
bitmask (5) = 0b11111 = 31
bitmask (0) = 0
首先,我有这个实现(mask_t
只是一个typedef
uint64_t
}:
mask_t bitmask (unsigned short n) {
return ((((mask_t) 1) << n) - 1;
}
一切都很好,除非函数点击bitmask (64)
(mask_t
的大小),然后我得bitmask (64) = 0
代替64位设置为1
。
所以,我有两个问题:
为什么我会有这种行为?在左侧按1
64个班次应该清除寄存器并保持0
,然后应用-1
应该用1
s填充寄存器......
实现此功能的正确方法是什么?
答案 0 :(得分:5)
是的,这是一个众所周知的问题。有很多简单的方法可以在0..63范围内和1..64范围内实现此功能(注释中已经提到了一种方法),但0..64更难。
当然你可以选择“左移”或“右移”掩码生成,然后选择特殊情况“失踪”n
,
uint64_t bitmask (unsigned short n) {
if (n == 64) return -((uint64_t)1);
return (((uint64_t) 1) << n) - 1;
}
或者
uint64_t bitmask (unsigned short n) {
if (n == 0) return 0;
uint64_t full = ~(uint64_t)0;
return full >> (64 - n);
}
无论哪种方式都倾向于编译到分支,尽管它在技术上并不 。
您可以在没有if
(未经测试)
uint64_t bitmask (unsigned int n) {
uint64_t x = (n ^ 64) >> 6;
return (x << (n & 63)) - 1;
}
这里的想法是,我们要么向左移动一些与原始代码相同的数量,要么在n = 64
的情况下为0。将0向左移0再次变为0,减去1组全部64位。
或者,如果您使用的是现代x64平台并且BZHI可用,则速度非常快(BZHI在所有实现它的CPU上都很快)但是有限的便携性选项是:
uint64_t bitmask (unsigned int n) {
return _bzhi_u64(~(uint64_t)0, n);
}
这甚至为n > 64
定义良好,1的实际计数将为min(n & 0xFF, 64)
,因为BZHI饱和但只读取索引的最低字节。
答案 1 :(得分:4)
您不能将移位大于或等于相关类型的位宽的值。这样做会调用undefined behavior。
来自C standard的第6.5.7节:
2 对每个操作数执行整数提升。该 结果的类型是提升的左操作数的类型。 如果值 右操作数是负数还是大于或等于 提升左操作数的宽度,行为未定义。
您需要在代码中添加一项检查:
mask_t bitmask (unsigned short n) {
if (n >= 64) {
return ~(mask_t)0;
} else {
return (((mask_t) 1) << n) - 1;
}
}
答案 2 :(得分:1)
最后,仅为了您的信息,我最后写了:
mask_t bitmask (unsigned short n) {
return (n < (sizeof (mask_t) * CHAR_BIT)) ? (((mask_t) 1) << n) - 1 : -1;
}
但是,哈罗德的答案是如此完整和充分解释,我会选择它作为答案。