在 javascript 中有效地获取最高和最低有效位
Efficiently getting most and least significant bit in javascript
64 位整数。我正在尝试获取最左边的非零位的索引。我使用遍历所有 64 位的天真方法来计算这个。
function getLowestBitPosition(bitstring) {
for (a = 0; a < bitstring.length; a++) if (bitstring[a] === "1") return a;
}
同样,我向后迭代得到最右边的非零位的索引。
function getHighestBitPosition(bitstring) {
for (a = bitstring.length - 1; a >= 0; a--) if (bitstring[a] === "1") return a;
}
我很确定有更有效的方法来实现它。我已经看到 C 中的方法需要很少的迭代。但是,所有这些示例都使用了 C 库函数。在javascript中获取索引有没有更好的方法?
由于您似乎无法访问这些整数的原始二进制形式,而是必须处理它们的位串表示,因此您只能使用字符串函数。
很可能内置函数 indexOf() and lastIndexOf() 会比 for 循环更快。
除此之外,我认为没有更快的方法可以在一串 1 和 0 中找到第一个设置位。
Ported/adapted 来自 this answer,这是从 BigInt 获取 MSB 或 LSB 位置的一种很好的、无字符串、无循环和无条件的方法。
const deBruijn = [0, 48, -1, -1, 31, -1, 15, 51, -1, 63, 5, -1, -1, -1, 19, -1, 23, 28, -1, -1, -1, 40, 36, 46, -1, 13, -1, -1, -1, 34, -1, 58, -1, 60, 2, 43, 55, -1, -1, -1, 50, 62, 4, -1, 18, 27, -1, 39, 45, -1, -1, 33, 57, -1, 1, 54, -1, 49, -1, 17, -1, -1, 32, -1, 53, -1, 16, -1, -1, 52, -1, -1, -1, 64, 6, 7, 8, -1, 9, -1, -1, -1, 20, 10, -1, -1, 24, -1, 29, -1, -1, 21, -1, 11, -1, -1, 41, -1, 25, 37, -1, 47, -1, 30, 14, -1, -1, -1, -1, 22, -1, -1, 35, 12, -1, -1, -1, 59, 42, -1, -1, 61, 3, 26, 38, 44, -1, 56];
const multiplicator = BigInt("0x6c04f118e9966f6b");
const
b1 = BigInt(1),
b2 = BigInt(2),
b4 = BigInt(4),
b8 = BigInt(8),
b16 = BigInt(16),
b32 = BigInt(32),
b57 = BigInt(57);
function msb(v) {
v |= v >> b1;
v |= v >> b2;
v |= v >> b4;
v |= v >> b8;
v |= v >> b16;
v |= v >> b32;
return deBruijn[
BigInt.asUintN(
64,
(BigInt.asUintN(
64,
(v * multiplicator))) >> b57)
];
}
function lsb(v) {
v = -v | v;
return deBruijn[
BigInt.asUintN(
64,
(BigInt.asUintN(
64,
(~(v) * multiplicator))) >> b57)
];
}
console.log(lsb(BigInt(18)))
console.log(msb(BigInt(18)))
意识到已经为这个问题选择了答案,但发现它是一个有趣的问题,可能最好用 webassembly (WASM) 来解决。不幸的是,WASM 似乎不支持 i64 参数,因此不得不将 BigInt 作为 lo / hi ( i32 / i32 ) 发送,并在寻找 MSB 之前在 WASM 中作为 i64 拼接在一起。
编辑 2021 年 1 月: 根据 https://webassembly.org/roadmap/,最新的浏览器现在支持 javascript 和 WASM 之间的 BigInt i64 参数。此答案已更新为现在包含以下 WASM 代码 lz64.wat
编译成 lz64.wasm
:
(module
(func $lz64 (param $val i64) (result i64)
get_local $val
i64.clz
)
(export "lz64" (func $lz64))
)
与下面需要拼接两个i32参数的WASM代码相比,上面的代码大大简化了(也快了很多)!
WASM i32 代码相当简单,利用 https://pengowray.github.io/wasm-ops/ to determine the proper OpCodes. Additionally, used https://webassembly.github.io/wabt/demo/wat2wasm/ 创建、调试 Web 程序集文本 (WAT) 并将其编译为 WASM。 i64.clz
操作是真正的魔法发生的地方。之前的所有代码都是将两个i32数字拼接在一起,形成实际的i64数字来检查。另请注意,WASM 的行为有点像 RPN 计算器...
下面的lz64v32.wat
编译成lz64v32.wasm
(module
(func $lz (param $lo i32) (param $hi i32) (result i32)
get_local $hi
i64.extend_i32_u
i64.const 32
i64.shl
get_local $lo
i64.extend_i32_u
i64.add
i64.clz
i32.wrap_i64
)
(export "lz" (func $lz))
)
下面的列表包括 De Bruijn 解决方案的答案,以测试相对性能。
EDIT 也无意中发现 指出了 Math.clz32 函数(!)。将一些帮助函数放在一起以支持查找 64 位 BigInt 的 MSB,并且 运行 也使用第三个选项进行测试。
小心! 运行 下面的代码将通过 Math.clz32、WASM 和 De Bruijn 解决方案创建 10M BigInts 到 运行。在我的 Acer 笔记本电脑上,在少数 运行s 上,结果是...
- Math.clz32 => 1.70s
- WASM i32 => 2.06s
- WASM i64 => 0.43s
- De Bruijn => 13.70s
WASM i64 解决方案明显更快!
var v232 = 2n ** 32n;
function clz32(n) {
return Math.clz32(n | 0);
}
//
function ctz32(n) {
n |= 0;
return n ? 31 - Math.clz32(n & -n) : 0;
}
function clz64(bn) {
let result = clz32(Number(bn / v232) | 0);
if (result === 32) {
result += clz32(Number(bn % v232) | 0);
}
return result;
}
function arrayBufferToBase64(arrayBuffer) {
return btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
}
function base64ToArrayBuffer(base64) {
let s = atob(base64);
let arrayBuffer = new ArrayBuffer(s.length);
var bufferView = new Uint8Array(arrayBuffer);
for (let i = 0; i < s.length; i++) {
bufferView[i] = s.charCodeAt(i);
}
return arrayBuffer;
}
async function compile(fn, preCompiled) {
let wasmBuffer;
if (preCompiled) {
wasmBuffer = base64ToArrayBuffer(preCompiled);
} else {
let response = await fetch(fn);
wasmBuffer = await response.arrayBuffer();
console.log(arrayBufferToBase64(wasmBuffer));
}
return await WebAssembly.instantiate(wasmBuffer);
}
async function runTest() {
let wasm64v32 = await compile('./lz64v32.wasm', 'AGFzbQEAAAABBwFgAn9/AX8DAgEABwYBAmx6AAAKEAEOACABrUIghiAArXx5pwsAGQRuYW1lAQUBAAJsegILAQACAAJsbwECaGk=');
let wasm64 = await compile('./lz64.wasm', 'AGFzbQEAAAABBgFgAX4BfgMCAQAHCAEEbHo2NAAACgcBBQAgAHkLABgEbmFtZQEHAQAEbHo2NAIIAQABAAN2YWw=');
let v232 = 2n ** 32n;
let randomBigInts = new Array(10000000);
console.log(`Building ${randomBigInts.length.toLocaleString()} BigInts...\n\n`);
for (let i = 0; i < randomBigInts.length; i++) {
randomBigInts[i] = 2n ** BigInt(Math.round(Math.random() * 64));
}
console.log(`Math.clz32 MSB start check on ${randomBigInts.length.toLocaleString()} BigInts.`);
randomBigInts.forEach(v=>{
64 - clz64(v);
});
console.log('Done');
let v = randomBigInts[0];
console.log(`Math.clz32 test of msb ${('0'.repeat(64) + v.toString(2)).slice(-64)} is ${64 - clz64(v)}\n\n`);
console.log(`WASM MSB start check on ${randomBigInts.length.toLocaleString()} BigInts via WASM, splitting 64 bit BigInt into 2 x i32 parameters.`);
let lzf = wasm64v32.instance.exports.lz
randomBigInts.forEach(v=>{
64 - lzf(Number(v % v232), Number(v / v232));
});
console.log('Done');
console.log(`WASM test of leading zeros ${('0'.repeat(64) + v.toString(2)).slice(-64)} is ${64 - lzf(Number(v % v232), Number(v / v232))}\n\n`);
console.log(`WASM MSB start check on ${randomBigInts.length.toLocaleString()} BigInts via WASM using i64 parameter.`);
let lzf64 = wasm64.instance.exports.lz64
randomBigInts.forEach(v=>{
64n - lzf64(v);
});
console.log('Done');
console.log(`WASM test of leading zeros ${('0'.repeat(64) + v.toString(2)).slice(-64)} is ${64n - lzf64(v)}\n\n`);
console.log(`DeBruijn MSB start check on ${randomBigInts.length.toLocaleString()} BigInts via deBruijn.`);
randomBigInts.forEach(v=>{
msb(v);
});
console.log('Done');
console.log(`DeBruijn test of leading zeros ${('0'.repeat(64) + v.toString(2)).slice(-64)} is ${msb(v)}\n\n`);
}
const deBruijn = [0, 48, -1, -1, 31, -1, 15, 51, -1, 63, 5, -1, -1, -1, 19, -1, 23, 28, -1, -1, -1, 40, 36, 46, -1, 13, -1, -1, -1, 34, -1, 58, -1, 60, 2, 43, 55, -1, -1, -1, 50, 62, 4, -1, 18, 27, -1, 39, 45, -1, -1, 33, 57, -1, 1, 54, -1, 49, -1, 17, -1, -1, 32, -1, 53, -1, 16, -1, -1, 52, -1, -1, -1, 64, 6, 7, 8, -1, 9, -1, -1, -1, 20, 10, -1, -1, 24, -1, 29, -1, -1, 21, -1, 11, -1, -1, 41, -1, 25, 37, -1, 47, -1, 30, 14, -1, -1, -1, -1, 22, -1, -1, 35, 12, -1, -1, -1, 59, 42, -1, -1, 61, 3, 26, 38, 44, -1, 56];
const multiplicator = BigInt("0x6c04f118e9966f6b");
const b1 = BigInt(1)
, b2 = BigInt(2)
, b4 = BigInt(4)
, b8 = BigInt(8)
, b16 = BigInt(16)
, b32 = BigInt(32)
, b57 = BigInt(57);
function msb(v) {
v |= v >> b1;
v |= v >> b2;
v |= v >> b4;
v |= v >> b8;
v |= v >> b16;
v |= v >> b32;
return deBruijn[BigInt.asUintN(64, (BigInt.asUintN(64, (v * multiplicator))) >> b57)];
}
function lsb(v) {
v = -v | v;
return deBruijn[BigInt.asUintN(64, (BigInt.asUintN(64, (~(v) * multiplicator))) >> b57)];
}
runTest();
小心! 运行 此代码将执行 40M 测试,在控制台日志中看到任何结果之前会暂停。
64 位整数。我正在尝试获取最左边的非零位的索引。我使用遍历所有 64 位的天真方法来计算这个。
function getLowestBitPosition(bitstring) {
for (a = 0; a < bitstring.length; a++) if (bitstring[a] === "1") return a;
}
同样,我向后迭代得到最右边的非零位的索引。
function getHighestBitPosition(bitstring) {
for (a = bitstring.length - 1; a >= 0; a--) if (bitstring[a] === "1") return a;
}
我很确定有更有效的方法来实现它。我已经看到 C 中的方法需要很少的迭代。但是,所有这些示例都使用了 C 库函数。在javascript中获取索引有没有更好的方法?
由于您似乎无法访问这些整数的原始二进制形式,而是必须处理它们的位串表示,因此您只能使用字符串函数。
很可能内置函数 indexOf() and lastIndexOf() 会比 for 循环更快。
除此之外,我认为没有更快的方法可以在一串 1 和 0 中找到第一个设置位。
Ported/adapted 来自 this answer,这是从 BigInt 获取 MSB 或 LSB 位置的一种很好的、无字符串、无循环和无条件的方法。
const deBruijn = [0, 48, -1, -1, 31, -1, 15, 51, -1, 63, 5, -1, -1, -1, 19, -1, 23, 28, -1, -1, -1, 40, 36, 46, -1, 13, -1, -1, -1, 34, -1, 58, -1, 60, 2, 43, 55, -1, -1, -1, 50, 62, 4, -1, 18, 27, -1, 39, 45, -1, -1, 33, 57, -1, 1, 54, -1, 49, -1, 17, -1, -1, 32, -1, 53, -1, 16, -1, -1, 52, -1, -1, -1, 64, 6, 7, 8, -1, 9, -1, -1, -1, 20, 10, -1, -1, 24, -1, 29, -1, -1, 21, -1, 11, -1, -1, 41, -1, 25, 37, -1, 47, -1, 30, 14, -1, -1, -1, -1, 22, -1, -1, 35, 12, -1, -1, -1, 59, 42, -1, -1, 61, 3, 26, 38, 44, -1, 56];
const multiplicator = BigInt("0x6c04f118e9966f6b");
const
b1 = BigInt(1),
b2 = BigInt(2),
b4 = BigInt(4),
b8 = BigInt(8),
b16 = BigInt(16),
b32 = BigInt(32),
b57 = BigInt(57);
function msb(v) {
v |= v >> b1;
v |= v >> b2;
v |= v >> b4;
v |= v >> b8;
v |= v >> b16;
v |= v >> b32;
return deBruijn[
BigInt.asUintN(
64,
(BigInt.asUintN(
64,
(v * multiplicator))) >> b57)
];
}
function lsb(v) {
v = -v | v;
return deBruijn[
BigInt.asUintN(
64,
(BigInt.asUintN(
64,
(~(v) * multiplicator))) >> b57)
];
}
console.log(lsb(BigInt(18)))
console.log(msb(BigInt(18)))
意识到已经为这个问题选择了答案,但发现它是一个有趣的问题,可能最好用 webassembly (WASM) 来解决。不幸的是,WASM 似乎不支持 i64 参数,因此不得不将 BigInt 作为 lo / hi ( i32 / i32 ) 发送,并在寻找 MSB 之前在 WASM 中作为 i64 拼接在一起。
编辑 2021 年 1 月: 根据 https://webassembly.org/roadmap/,最新的浏览器现在支持 javascript 和 WASM 之间的 BigInt i64 参数。此答案已更新为现在包含以下 WASM 代码 lz64.wat
编译成 lz64.wasm
:
(module
(func $lz64 (param $val i64) (result i64)
get_local $val
i64.clz
)
(export "lz64" (func $lz64))
)
与下面需要拼接两个i32参数的WASM代码相比,上面的代码大大简化了(也快了很多)!
WASM i32 代码相当简单,利用 https://pengowray.github.io/wasm-ops/ to determine the proper OpCodes. Additionally, used https://webassembly.github.io/wabt/demo/wat2wasm/ 创建、调试 Web 程序集文本 (WAT) 并将其编译为 WASM。 i64.clz
操作是真正的魔法发生的地方。之前的所有代码都是将两个i32数字拼接在一起,形成实际的i64数字来检查。另请注意,WASM 的行为有点像 RPN 计算器...
lz64v32.wat
编译成lz64v32.wasm
(module
(func $lz (param $lo i32) (param $hi i32) (result i32)
get_local $hi
i64.extend_i32_u
i64.const 32
i64.shl
get_local $lo
i64.extend_i32_u
i64.add
i64.clz
i32.wrap_i64
)
(export "lz" (func $lz))
)
下面的列表包括 De Bruijn 解决方案的答案,以测试相对性能。
EDIT 也无意中发现
小心! 运行 下面的代码将通过 Math.clz32、WASM 和 De Bruijn 解决方案创建 10M BigInts 到 运行。在我的 Acer 笔记本电脑上,在少数 运行s 上,结果是...
- Math.clz32 => 1.70s
- WASM i32 => 2.06s
- WASM i64 => 0.43s
- De Bruijn => 13.70s
WASM i64 解决方案明显更快!
var v232 = 2n ** 32n;
function clz32(n) {
return Math.clz32(n | 0);
}
//
function ctz32(n) {
n |= 0;
return n ? 31 - Math.clz32(n & -n) : 0;
}
function clz64(bn) {
let result = clz32(Number(bn / v232) | 0);
if (result === 32) {
result += clz32(Number(bn % v232) | 0);
}
return result;
}
function arrayBufferToBase64(arrayBuffer) {
return btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
}
function base64ToArrayBuffer(base64) {
let s = atob(base64);
let arrayBuffer = new ArrayBuffer(s.length);
var bufferView = new Uint8Array(arrayBuffer);
for (let i = 0; i < s.length; i++) {
bufferView[i] = s.charCodeAt(i);
}
return arrayBuffer;
}
async function compile(fn, preCompiled) {
let wasmBuffer;
if (preCompiled) {
wasmBuffer = base64ToArrayBuffer(preCompiled);
} else {
let response = await fetch(fn);
wasmBuffer = await response.arrayBuffer();
console.log(arrayBufferToBase64(wasmBuffer));
}
return await WebAssembly.instantiate(wasmBuffer);
}
async function runTest() {
let wasm64v32 = await compile('./lz64v32.wasm', 'AGFzbQEAAAABBwFgAn9/AX8DAgEABwYBAmx6AAAKEAEOACABrUIghiAArXx5pwsAGQRuYW1lAQUBAAJsegILAQACAAJsbwECaGk=');
let wasm64 = await compile('./lz64.wasm', 'AGFzbQEAAAABBgFgAX4BfgMCAQAHCAEEbHo2NAAACgcBBQAgAHkLABgEbmFtZQEHAQAEbHo2NAIIAQABAAN2YWw=');
let v232 = 2n ** 32n;
let randomBigInts = new Array(10000000);
console.log(`Building ${randomBigInts.length.toLocaleString()} BigInts...\n\n`);
for (let i = 0; i < randomBigInts.length; i++) {
randomBigInts[i] = 2n ** BigInt(Math.round(Math.random() * 64));
}
console.log(`Math.clz32 MSB start check on ${randomBigInts.length.toLocaleString()} BigInts.`);
randomBigInts.forEach(v=>{
64 - clz64(v);
});
console.log('Done');
let v = randomBigInts[0];
console.log(`Math.clz32 test of msb ${('0'.repeat(64) + v.toString(2)).slice(-64)} is ${64 - clz64(v)}\n\n`);
console.log(`WASM MSB start check on ${randomBigInts.length.toLocaleString()} BigInts via WASM, splitting 64 bit BigInt into 2 x i32 parameters.`);
let lzf = wasm64v32.instance.exports.lz
randomBigInts.forEach(v=>{
64 - lzf(Number(v % v232), Number(v / v232));
});
console.log('Done');
console.log(`WASM test of leading zeros ${('0'.repeat(64) + v.toString(2)).slice(-64)} is ${64 - lzf(Number(v % v232), Number(v / v232))}\n\n`);
console.log(`WASM MSB start check on ${randomBigInts.length.toLocaleString()} BigInts via WASM using i64 parameter.`);
let lzf64 = wasm64.instance.exports.lz64
randomBigInts.forEach(v=>{
64n - lzf64(v);
});
console.log('Done');
console.log(`WASM test of leading zeros ${('0'.repeat(64) + v.toString(2)).slice(-64)} is ${64n - lzf64(v)}\n\n`);
console.log(`DeBruijn MSB start check on ${randomBigInts.length.toLocaleString()} BigInts via deBruijn.`);
randomBigInts.forEach(v=>{
msb(v);
});
console.log('Done');
console.log(`DeBruijn test of leading zeros ${('0'.repeat(64) + v.toString(2)).slice(-64)} is ${msb(v)}\n\n`);
}
const deBruijn = [0, 48, -1, -1, 31, -1, 15, 51, -1, 63, 5, -1, -1, -1, 19, -1, 23, 28, -1, -1, -1, 40, 36, 46, -1, 13, -1, -1, -1, 34, -1, 58, -1, 60, 2, 43, 55, -1, -1, -1, 50, 62, 4, -1, 18, 27, -1, 39, 45, -1, -1, 33, 57, -1, 1, 54, -1, 49, -1, 17, -1, -1, 32, -1, 53, -1, 16, -1, -1, 52, -1, -1, -1, 64, 6, 7, 8, -1, 9, -1, -1, -1, 20, 10, -1, -1, 24, -1, 29, -1, -1, 21, -1, 11, -1, -1, 41, -1, 25, 37, -1, 47, -1, 30, 14, -1, -1, -1, -1, 22, -1, -1, 35, 12, -1, -1, -1, 59, 42, -1, -1, 61, 3, 26, 38, 44, -1, 56];
const multiplicator = BigInt("0x6c04f118e9966f6b");
const b1 = BigInt(1)
, b2 = BigInt(2)
, b4 = BigInt(4)
, b8 = BigInt(8)
, b16 = BigInt(16)
, b32 = BigInt(32)
, b57 = BigInt(57);
function msb(v) {
v |= v >> b1;
v |= v >> b2;
v |= v >> b4;
v |= v >> b8;
v |= v >> b16;
v |= v >> b32;
return deBruijn[BigInt.asUintN(64, (BigInt.asUintN(64, (v * multiplicator))) >> b57)];
}
function lsb(v) {
v = -v | v;
return deBruijn[BigInt.asUintN(64, (BigInt.asUintN(64, (~(v) * multiplicator))) >> b57)];
}
runTest();
小心! 运行 此代码将执行 40M 测试,在控制台日志中看到任何结果之前会暂停。