Integer.bitCount()関数の理解(可能な限り理解しやすい)

bitCount(int i)関数は、数値の2進数で1の数をカウントすることを実現します。たとえば、5のバイナリ値は101であり、2が返されます。

Jdk1.8のソースコードは以下の通りです。一見唖然としましたが、それでも唖然としました。2時間の分析の後、唖然とし、元気になったので、この記事を書きました。

public static int bitCount(int i) {
    
    
   // HD, Figure 5-2
    i = i - ((i >>> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
    i = (i + (i >>> 4)) & 0x0f0f0f0f;
    i = i + (i >>> 8);
    i = i + (i >>> 16);
    return i & 0x3f;
}

基本知識

  • &および操作:a&b、aとbの両方が1で、結果は1です。それ以外の場合、結果は0です。1001 1100、最初の4桁の1001 1100&1111 0000 = 1001、最後の4桁の1001 1100&0000 1111 = 1100などの固定間隔の値を取得するために使用できます。
  • ">>>"および "<<<":符号なし右シフトおよび符号なし左シフト。(負の数を表す方法が含まれますが、これはこの記事の範囲を超えています)
  • コード内の16進数の2進表現は次のとおりです。
元の バイナリ
0x55555555 01010101 01010101 01010101 01010101
0x33333333 00110011 00110011 0011001100110011
0x0f0f0f0f 00001111 00001111 00001111 00001111
0x3f 00000000 00000000 00000000 11111111

結果から始める

  • Intは4バイトを占め、C1、C2、C3、C4はこれらの4バイトを左から右に表します。入力パラメーターiはC1、C2、C3、C4で表され、一連の計算の後、iはD1、D2、D3、およびD4で表されます。

  • 戻り値による i&0x3fことを見ることができる唯一のD4は、操作に関与しているとD4の値は、結果の値ですD4の値だけが結果になるのはなぜですか?答えはコードの最後の2行にあります

i = i + (i >>> 8);// 第一行
i = i + (i >>> 16);// 第二行
  • コードの最後の2行のロジックは次のとおりです。バイトには8ビットがあり、8ビットを右シフトすると最後のバイトが削除されます。同様に16ビットを右シフトすると最後の2バイトが削除されます。これらの2行のコードが実行された後、D4の値は実際にはD1 + D2 + D3 + D4です。したがって、D4を使用して計算に参加し、戻ります。
    ここに画像の説明を挿入

分割統治

  • タイムラインがコードの最後の2行に戻る前に、D1、D2、D3、およびD4は何を表していますか?なぜ加算が結果になるのですか?
  • 結論:D1は、C1に1がいくつあるかを示します。同様に、D2、D3、およびD4は、それぞれC2、C3、およびC4にある1の数を示します。したがって、結果としてD1 + D2 + D3 + D4を返すことができます。
  • では、C1はどのようにD1に移動するのでしょうか。これは、コードの最初の3行が行うことです
i = i - ((i >>> 1) & 0x55555555); 
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
  • C1が10110011であると仮定します。別の観点からC1の各ビットの意味を見てみましょう。各ビットは、このビットに1がいくつあるかを示します。1のビットはこのビットが1であることを意味し、0のビットはこのビットが0であることを意味します。コードの最初の行が実行された後、このアイデアは意味のあるものになります。2桁は、これら2桁に1がいくつあるかを示すために使用されます。たとえば、最初の2桁10は、01を使用してこれら2桁に1がいくつあるかを示し、3桁または4桁11は10を使用して示すことができます。これらの2桁に1がいくつあるかなど。
  • コードの最初の行が実行された後、C1 '= 01 10 00 10は、1桁または2桁に1があり、3桁または4桁に2があり、5桁または6桁に0があり、7桁または8桁にあることを意味します。 2つの1。
  • コードの2行目が実行された後、C1 '' = 0011 0010は、最初の4桁に3つの1があり、最後の4桁に2つの1があることを意味します。
  • コードの3行目が実行された後、D1 = 00000101、小数は5です。これは、サブ結果としてD2、D3、およびD4に直接追加して、返すことができます。

実行プロセスは次のとおりです。
ここに画像の説明を挿入

コードの最初の3行を理解する

  • コードの最後の2行は、以前に分析されています。コードの最初の3行の効果はすでにわかっていますが、上記の効果を実現する方法は依然として厄介であり、同等の記​​述に変更する方が理解しやすく、もちろんソースコードの効率は高くなります。
源码:
i = i - ((i >>> 1) & 0x55555555); 
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;

等价写法:
i = (i & 0x55555555) + ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i & 0x0f0f0f0f) + ((i >>> 4) & 0x0f0f0f0f);
  • C1 = 10110011を例にとると、各ビットは前のナンセンスに対応する間隔を表します。ビットビットが1の場合は、このビットが1であることを意味し、ビットビットが0の場合は、このビットの1が0であることを意味します説明には1〜8の間隔が便利です。奇数の位置を「奇数の間隔」、偶数の位置を「偶数の間隔」と呼びます。
  • コードの最初の行のプラス記号の前では、偶数間隔で1が取得され、プラス記号の後では、奇数間隔で1が取得されます。加算は、隣接する「パリティ間隔」との間隔をマージすることと同じです。 C1は4に減少します。
  • 同様に、コードの2行目は間隔を2に減らし、コードの3行目は間隔を1に減らします。これに基づいて、C1からD1を完成させます。
  • 全体のプロセスは、実際には分割統治の逆の順序です〜

最後に、上記のプロセスは紙に描くことができ、一目でわかります。

おすすめ

転載: blog.csdn.net/qq_27007509/article/details/112246576