如何使用基于 table 的方法在 JavaScript 中找到 8 位、16 位和 32 位整数的前导 ones/zeroes?

How to find leading ones/zeroes for 8-bit, 16-bit, and 32-bit integers in JavaScript, using a table-based approach?

在网上搜索了几个小时这个,但只找到了以下内容。首先,JavaScript有Math.clz32(x),所以你可以C倒数LZeroes 在 32 位值上。一切都很好,但我想知道这些是如何实现的。

const trailingZeroesTable = [
  32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30,
  28, 11, 0, 13, 4, 7,
  17, 0, 25, 22, 31, 15,
  29, 10, 12, 6, 0, 21,
  14, 9, 5, 20, 8, 19, 18
];

const leadingZeroesTable = [
  31, 22, 30, 21, 18, 10, 29, 2,
  20, 17, 15, 13, 9, 6, 28, 1,
  23, 19, 11, 3, 16, 14, 7, 24,
  12, 4, 8, 25, 5, 26, 27, 0
]

function countTrailingZeroes32(x) {
  // Only difference between
  // (x and -x) is the value
  // of signed magnitude
  // (leftmostbit) negative
  // numbers signed bit is 1
  return trailingZeroesTable[(-x & x) % 37];
}

function countLeadingZeroes32(x) {
  x |= x >> 1n;
  x |= x >> 2n;
  x |= x >> 4n;
  x |= x >> 8n;
  x |= x >> 16n;
  return leadingZeroesTable[((x * 0x07c4acddn) & 0xffffffffn) >> 27n];
}

console.log('countLeadingZeroes32', countLeadingZeroes32(0b00000011000011110000111100001111n))
console.log('countTrailingZeroes32', countTrailingZeroes32(0b00000011000011110000110000000000))

计算 16 位和 8 位整数的尾随零似乎能够使用相同的 table,尽管也许有更好的 table?我想重用也很好。

const trailingZeroesTable = [
  32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30,
  28, 11, 0, 13, 4, 7,
  17, 0, 25, 22, 31, 15,
  29, 10, 12, 6, 0, 21,
  14, 9, 5, 20, 8, 19, 18
];

function countTrailingZeroes32(x) {
  // Only difference between
  // (x and -x) is the value
  // of signed magnitude
  // (leftmostbit) negative
  // numbers signed bit is 1
  return trailingZeroesTable[(-x & x) % 37];
}

function countTrailingZeroes16(x) {
  return countTrailingZeroes32(x)
}

function countTrailingZeroes8(x) {
  return countTrailingZeroes32(x)
}

console.log('countTrailingZeroes32', countTrailingZeroes32(0b00000011000011110000110000000000))
console.log('countTrailingZeroes16', countTrailingZeroes16(0b0000001100001100))
console.log('countTrailingZeroes8', countTrailingZeroes8(0b00100000))

所以问题的剩余未解决部分是:

  1. 如何使用“table 方法”实现 countLeadingZeroes32 不使用 使用 BigInt(在 JavaScript 中),并且不求助于 Math.clz32(x)Math.log 等库函数?只有基本知识。
  2. 如何使用 table 方法在 JavaScript 中实现 countLeadingZeroes[8/16](8 位和 16 位)?
  3. 如何使用 table 方法在 JavaScript 中实现 countLeadingOnes[8/16/32]

Here 是一个 countTrailingOnes,它似乎只做 countTrailingZeroes(~x),所以这似乎有效:

const trailingZeroesTable = [
  32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30,
  28, 11, 0, 13, 4, 7,
  17, 0, 25, 22, 31, 15,
  29, 10, 12, 6, 0, 21,
  14, 9, 5, 20, 8, 19, 18
]

function countTrailingZeroes32(x) {
  // Only difference between
  // (x and -x) is the value
  // of signed magnitude
  // (leftmostbit) negative
  // numbers signed bit is 1
  return trailingZeroesTable[(-x & x) % 37];
}

function countTrailingOnes32(x) {
  return countTrailingZeroes32(~x)
}

function countTrailingOnes16(x) {
  return countTrailingZeroes32(~x)
}

function countTrailingOnes8(x) {
  return countTrailingZeroes32(~x)
}

console.log('countTrailingOnes32', countTrailingOnes32(0b00000011000011110000110000011111))
console.log('countTrailingOnes16', countTrailingOnes16(0b0000001100001111))
console.log('countTrailingOnes8', countTrailingOnes8(0b00000011))

主要是,如何在 JavaScript 中 countLeadingZeroes[8/16/32]countLeadingOnes[8/16/32](可能只是 countLeadingZeroes(~x)),而不使用字符串 hack 或 BigInt。也就是说,通过使用优化的预计算 table 方法。

理想情况下,我们将为前导和尾随 table 提供一个 table 生成函数,但对于此 post 并非完全必要。除了预先计算的 table,我什么都找不到,还没有生成它的算法。

从最初的问题,我算出了计数 ones/zeroes:

const COUNT_BITS_TABLE = makeLookupTable()

function makeLookupTable() {
  const table = new Array(256).fill(0)
  // generate the lookup table
  for (let i = 0; i < 256; i++) {
    table[i] = (i & 1) + table[(i / 2) | 0];
  }
  return table
}

function countOneBits32(n) {
  return COUNT_BITS_TABLE[n & 0xff] +      // consider the first 8 bits
    COUNT_BITS_TABLE[(n >> 8) & 0xff] +       // consider the next 8 bits
    COUNT_BITS_TABLE[(n >> 16) & 0xff] +      // consider the next 8 bits
    COUNT_BITS_TABLE[(n >> 24) & 0xff];
}

function countOneBits16(n) {
  return COUNT_BITS_TABLE[n & 0xff] +      // consider the first 8 bits
    COUNT_BITS_TABLE[(n >> 8) & 0xff]
}

function countOneBits8(n) {
  return COUNT_BITS_TABLE[n & 0xff]
}

console.log('countOneBits32', countOneBits32(0b10101010000000001010101000000000))
console.log('countOneBits32', countOneBits32(0b10101011110000001010101000000000))
console.log('countOneBits16', countOneBits16(0b1010101000000000))
console.log('countOneBits8', countOneBits8(0b10000010))

trailing-zeroes 函数的 table 仅用于查找 2 的幂的指数:-x & x returns 具有相同尾数的 2 的幂零位为 x。这意味着您可以 re-use 与 look-up table 相同的 leading-zeroes 函数,因为它使用的技术基本相同;它“涂抹”尾随 1 位以获得与原始输入具有相同数量前导零位的数字,并且后面的所有位都等于 1。增加该数字再次得到 2 的幂。

function countLeadingZeroes32(x) {
  x |= x >> 1;
  x |= x >> 2;
  x |= x >> 4;
  x |= x >> 8;
  x |= x >> 16;
  return 32 - trailingZeroesTable[((x + 1) & 0xffffffff) % 37];
}

最简单且非常 dirty-JS 的解决方案可能是:

function countLeadingZeroes32(x){
    return Math.max(0, 32-x.toString(2).length)
}

它将数字转换为二进制字符串并简单地计算其长度