ビットカウント(ポピュレーションカウント / popcount)
バイナリ数値の設定ビット(1)の数を数えます。Brian Kernighanのトリック、ルックアップテーブル、ハードウェアpopcount命令を学びます。
Bit Tricks
詳細な説明
ポピュレーションカウント(popcount)
数値のポピュレーションカウントは、バイナリ表現で1に設定されたビットの数です。ハミング重みまたは横方向の合計とも呼ばれます。
Brian Kernighanのアルゴリズム
最もエレガントなソフトウェアアプローチは、各反復で1つの設定ビットをクリアします:
function popcount(n) {
let count = 0;
while (n) {
n &= (n - 1); // 最下位の設定ビットをクリア
count++;
}
return count;
}
仕組み: n & (n - 1)は最下位の設定ビットをクリアします。各反復でちょうど1つの1ビットが削除されるため、ループは正確にpopcount(n)回実行されます。
例:popcount(42)
42 = 00101010 (3つの設定ビット)
反復1: 00101010 & 00101001 = 00101000 (ビット1クリア)
反復2: 00101000 & 00100111 = 00100000 (ビット3クリア)
反復3: 00100000 & 00011111 = 00000000 (ビット5クリア)
結果: 3
ルックアップテーブル方式
最大スループットのために、8ビット値の256エントリテーブルを事前計算し、1バイトずつ処理:
const TABLE = new Uint8Array(256);
for (let i = 1; i < 256; i++) {
TABLE[i] = TABLE[i >> 1] + (i & 1);
}
function popcount32(n) {
return TABLE[n & 0xFF] +
TABLE[(n >> 8) & 0xFF] +
TABLE[(n >> 16) & 0xFF] +
TABLE[(n >> 24) & 0xFF];
}
ハードウェアサポート
最新のCPUには専用のpopcount命令があります(x86のPOPCNT、ARMのCNT)。C/C++では__builtin_popcount()でアクセスできます。JavaScriptのMath.clz32()はリーディングゼロのカウントを提供しますが、組み込みのpopcountはありません。
ハミング距離
2つの値間のハミング距離はpopcount(a ^ b)です — ビット位置が異なる数です。
ユースケース
データベースエンジンはビットマップインデックスにpopcountを使用します。列指向データベースでは、列の各値をビットマップのビットとして表現できます。複雑な述語に一致する行を数えるには、関連するビットマップをAND/ORし、結果にpopcountを実行します。ClickHouseやApache DruidなどのシステムはSIMD popcount命令を使用して毎秒数十億行を処理します。