'How to find leading ones/zeroes for 8-bit, 16-bit, and 32-bit integers in JavaScript, using a table-based approach?
Searched for a few hours on the web for this one, but only found the following. First, JavaScript has Math.clz32(x)
, so you can Count Leading Zeroes on the 32-bit value. That is all fine and well, but I want to know how these are implemented.
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))
Counting trailing zeroes on 16-bit and 8-bit integers appears to be able to use the same table, though perhaps there is a better table for these? I guess reuse is good too.
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))
So the remaining unsolved bits parts of the question are:
- How to implement
countLeadingZeroes32
using the "table approach", without usingBigInt
(in JavaScript), and without resorting to library functions likeMath.clz32(x)
,Math.log
, etc.? Only bare basics. - How to implement
countLeadingZeroes[8/16]
(8-bit and 16-bit) in JavaScript using the table approach? - How to implement
countLeadingOnes[8/16/32]
in JavaScript using the table approach?
Here is a countTrailingOnes
, which appears to just do countTrailingZeroes(~x)
, so that seems to work:
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))
The main thing is, how to countLeadingZeroes[8/16/32]
and countLeadingOnes[8/16/32]
(which might just be countLeadingZeroes(~x)
) in JavaScript, without using string hacks or BigInt. That is, by using an optimized precomputed table approach.
Ideally we would have a table-generating function for both the leading and trailing tables, but not totally necessary for this post. I couldn't find anything but the precomputed table, no algorithm for generating it yet.
From the original question, I figured out the count 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))
Solution 1:[1]
The table for the trailing-zeroes function only exists to look up exponents for powers of two: -x & x
returns the power of two with the same number of trailing zero bits as x
. This means you can re-use the same look-up table for the leading-zeroes function, as the technique it uses is largely the same; it ‘smears’ trailing 1 bits to obtain a number with the same number of leading zero bits as the original input, and all bits that follow equal to 1. Incrementing that number gives you a power of two again.
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];
}
Solution 2:[2]
Easiest and very dirty-JS solution would probably be:
function countLeadingZeroes32(x){
return Math.max(0, 32-x.toString(2).length)
}
Which converts the number to a binary string and simply counts its length
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | user3840170 |
Solution 2 | Nico |