将n个连续位设置为1的最有效方法是什么?

时间:2017-07-27 13:33:06

标签: c bit-manipulation

我想获得一个函数,将数字类型的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. 为什么我会有这种行为?在左侧按1 64个班次应该清除寄存器并保持0,然后应用-1应该用1 s填充寄存器......

  2. 实现此功能的正确方法是什么?

3 个答案:

答案 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;
}

但是,哈罗德的答案是如此完整和充分解释,我会选择它作为答案。