在 JavaScript 中创建可变宽度位掩码(0-32 位)

Create variable width bitmasks (0-32 bits) in JavaScript

我正在寻找一种在 JavaScript 中生成宽度从 0 到 32 位的位掩码的方法。这必须适用于从 0 到 32 的所有宽度,因此:

表达式 (1 << width) - 1 适用于从 0 到 30 的宽度。

是否有一个简单的表达式(最好没有 if/else 分支)可以针对整个输入范围 (0-32) 完成此操作?

此测试代码说明了我当前的方法如何对 31 和 32 失败:

function bitmask(width) {
  return (1 << width) - 1;
}

function test(width){
  var result = bitmask(width).toString(2);
  console.log(width, 
              result, 
              result.replace(/[^1]/g,'').length === width? 'Ok': 'Fail');
}

test(0);
test(1);
test(5);
test(17);
test(30);
test(31); // fails
test(32); // fails

问题

在JavaScript中,按位运算符将操作数视为 32 位整数。来自 the MDN Documentation:

The operands of all bitwise operators are converted to signed 32-bit integers in two's complement format.

这意味着用31位来表示实际的数字,用1位(最左边)来表示符号。所以尝试做类似 x << 31 的事情会导致溢出并产生错误的结果。

解决方法

您应该考虑使用不同的方法,例如 Math.pow(),以获得更长的位掩码(和更高的值)。这是一个例子:

function bitmask(width) {
    return Math.pow(2, width) - 1;
}

现在,假设 JavaScript 使用 IEEE Standard 754 浮点表示法,此函数也有限制:它仅适用于短于 54 位的位掩码。例如,bitmask(54) 产生的整数比正确的高一个单位,bitmask(55) 产生的整数比正确的高三个单位,依此类推,错误会随着位掩码宽度的增加而增大。

另请注意,即使此函数可以生成长于 31 位的位掩码,但由于上述原因,这些位掩码仍不能与按位运算符一起使用。

使用 Math.pow() 而不是移位。这将起作用,因为 Javascript 中的所有数字都是 64 位 "double" 精度 IEE754 浮点数。

function bitmask(width) {
  return Math.pow(2, width) - 1;
}

function test(width){
  var result = bitmask(width).toString(2);
  console.log(width, 
              result, 
              result.replace(/[^1]/g,'').length === width? 'Ok': 'Fail');
}

test(0);
test(1);
test(5);
test(17);
test(30);
test(31);
test(32);

鉴于表达式 -1 >>> (32 - width) 适用于输入宽度 1–32,另一种可能的解决方案是表达式 width && -1 >>> 32 - width,它适用于整个输入范围。

虽然比接受的答案更难阅读,但这个表达式还有其他好处:

  1. 它避免了函数调用(对Math.pow
  2. 可以用更少的字符来写(为了打代码):
    • w&&-1>>>32-w(12 个字符),对比
    • Math.pow(2,w)-1, (15 个字符)

演示代码:

function bitmask(width) {
  return width && -1 >>> 32 - width;
}

// demonstrate that it works

for(var i = 0; i <= 32; i++) {
  console.log(i, bitmask(i).toString(2));
}