積分値から特定の位置でビットを取得する
1. 概要
数値の2進数が設定されているかどうかをテストする必要がある場合があります。 これは、各桁が特定のブール値を表すフラグのセットとして数値を使用していることが原因である可能性があります。
このチュートリアルでは、 byte 、 short 、 char 、[X159Xなどの整数値から特定の位置にビットを取得するさまざまな方法について説明します。 ] int 、およびlong。
2. 特定のビットをテストする
最も一般的な状況の1つは、bitmaskを使用して整数値の特定のビットをテストすることです。
たとえば、3番目のビットがbyte値に設定されているかどうかを確認してみましょう。
byte val1 = 0b0110_0100;
byte mask = 0b0000_0100;
boolean isSet1 = (val1 & mask) > 0;
assertTrue(isSet1);
ここでは、2進数 01100100 をテストして、3番目のビット– 00000100がビット単位のANDを使用して設定されているかどうかを確認します。 結果はゼロより大きいので、そうです。 設定されていないかどうかをテストすることもできます。
byte val2 = 0b0110_0010;
boolean isSet2 = (val2 & mask) > 0;
assertFalse(isSet2);
この例は、 byte 数値型に基づいており、 short 、 char 、 int 、およびに簡単に拡張できます。 ]long値。
このソリューションでは、ビットマスクをハードコーディングしました。 ソリューションを一般化して、番号のビットをチェックしたい場合はどうなりますか?
3. シフト演算子の使用
始める前に、まず32ビットintのビット位置のインデックス範囲を定義しましょう。 左端のビットのインデックスは31で、右端のビットのインデックスは0です。 これは、数値が最上位桁から最下位桁まで続くためです。 たとえば、64ビットの long 番号を使用した場合、左端のビットは63になります。
3.1. マスクを左シフト
値1を取得し、左シフト演算子を使用して正しい位置に移動することにより、ビットマスクを生成できます。
int val = 0b0110_0100;
int pos = 2;
int mask = 1 << pos;
boolean isSet = (val & mask) > 0;
assertTrue(isSet);
ここでは、posを2に設定していますが、これは数値の任意の有効なビット位置である可能性があります。 次に、左シフト演算子( << )ビットマスクを生成します。 最後に、ビット単位のAND( & )間の操作 val そしてそのマスク 。
結果がゼロより大きい場合は、ターゲットビットが設定されていることを意味します。
3.2. 値を左シフト
さらに、この問題を解決する別の方法があります。
ビットマスクを作成する代わりに、テストしている値に対して左シフト演算子を使用できます。 ビットマスクで値をフィルタリングするのではなく、対象のビットが左端の位置になるように内容をシフトできます。
次に、左端のビットが設定されているかどうかを確認するだけです。 符号付き整数は2の補数として表されるため、結果のビットシフト数が負であるかどうかをテストすることにより、先頭の桁が1であるかどうかをテストできます。
int val = 0b0110_0100;
int pos = 2;
boolean isSet = ((val << (31 - pos)) < 0);
assertTrue(isSet);
上記では、 pos は2で、左端の位置は31なので、31を使用してposを減算します。これは29に相当します。 次に、元の値を29ビット位置で左シフトし、新しい値を取得します。 この新しい値では、興味深いビットは左端の位置にあります。 最後に、新しい値がゼロ未満であるかどうかを確認します。
3.3. 値を右シフト
同様に、右シフト演算子を使用して、整数値のビットをテストできます。 整数値のターゲットビットを右端の位置に移動し、 1、のビットマスクを使用した後、結果が1に等しいかどうかを確認できます。
int val = 0b0110_0100;
int pos = 2;
boolean isSet = ((val >> pos) & 1) == 1;
assertTrue(isSet);
4. ビット単位ソリューションの最適化
これらの計算を頻繁に実行する可能性がある場合は、CPU命令の数を最小限に抑えるようにソリューションを最適化することをお勧めします。
これを達成するのに役立つかもしれない左シフトソリューションの書き直しを見てみましょう。 これは、ビット単位の演算は通常、算術演算よりも高速であるという仮定に基づいています。
boolean isSet = ((val << (~pos & 31)) < 0);
核となる考え方は変わっていないことに注意する必要があります。 コードの記述だけが微妙に異なります。 (〜pos&31) 前のものを置き換える (31-pos) 表現。
これらの2つの式が同じ効果を持つのはなぜですか? このプロセスを差し引くことができます:
(31 - pos) = (31 - pos) & 31
= (31 + (-pos)) & 31
= (31 & 31) + ((-pos) & 31)
= (31 & 31) + ((~pos + 1) & 31)
= (31 & 31) + (~pos & 31) + (1 & 31)
= ((31 + 1) & 31) + (~pos & 31)
= (32 & 31) + (~pos & 31)
= 0 + (~pos & 31)
= (~pos & 31)
このセクションの冒頭で、左端の位置が31で、右端の位置が0であると述べたので、(31 – pos)は正の数またはゼロである必要があります。 ビット単位のAND( & )間の操作 (31 – pos) 31、結果は同じままです。 次に、段階的に実行します。 最後に、 (〜pos&31) 表現。
このプロセスでは、もう1つ説明が必要です。(-pos)はどのように(〜pos + 1)に変換されますか? 整数の2の補数の負の表記を取得するには、ビット単位のCOMPLEMENT (〜)演算を実行し、結果に1を加算します。
さらに一歩進んで、コードをもう少し簡潔にすることができます。
boolean isSet = ((val << ~pos) < 0);
上記では、ビット単位のAND( & )および31。 これは、JVMが私たちのために仕事をしてくれるからです。 int 値は32ビットであり、JVMはその有効なシフト範囲が0から31の間であることを保証します。 同様に、 long 値は64ビットであり、JVMは、有効なシフト範囲が0〜63であることを確認します。
5. BigIntegerを使用する
上記のバイナリ計算は、組み込みの数値タイプの場合に最も計算効率が高くなりますが、64ビットを超える数値のビットをチェックする必要がある場合や、コードを読みやすくしたい場合があります。
BigInteger クラスは、これら両方の問題を解決できます。 膨大な数のビットを含む非常に大きな数をサポートし、testBitメソッドも提供します。
int val = 0b0110_0100;
int pos = 2;
boolean isSet = BigInteger.valueOf(val).testBit(pos);
assertTrue(isSet);
6. 結論
このチュートリアルでは、積分値から特定の位置にビットを取得するためのいくつかの一般的なアプローチを見ていきました。
いつものように、このチュートリアルのソースコードはGitHubのにあります。