web-dev-qa-db-ja.com

ビットごとの演算子を使用して1つのintに複数の値をパックする

低レベルのビット操作は私の強みではありませんでした。次のビット単位演算子の使用例を理解するのに役立ちます。検討してください...

int age, gender, height, packed_info;

. . .   // Assign values 

// Pack as AAAAAAA G HHHHHHH using shifts and "or"
packed_info = (age << 8) | (gender << 7) | height;

// Unpack with shifts and masking using "and"
height = packed_info & 0x7F;   // This constant is binary ...01111111
gender = (packed_info >> 7) & 1;
age    = (packed_info >> 8);

このコードが何をどのように達成しているかはわかりませんか?マジックナンバー0x7Fを使用する理由梱包と開梱はどのように行われますか?

ソース

31
maxpayne

コメントにあるように、年齢、性別、身長を15ビットの形式にパックします。

_AAAAAAAGHHHHHHH
_

この部分から始めましょう:

_(age << 8)
_

まず、年齢の形式は次のとおりです。

_age           = 00000000AAAAAAA
_

ここで、各Aは0または1です。

_<< 8_は、ビットを8桁左に移動し、ギャップをゼロで埋めます。だからあなたは得る:

_(age << 8)    = AAAAAAA00000000
_

同様に:

_gender        = 00000000000000G
(gender << 7) = 0000000G0000000
height        = 00000000HHHHHHH
_

次に、これらを1つの変数に結合します。 _|_演算子は、各ビットを調べて、どちらかの入力でビットが1の場合に1を返します。そう:

_0011 | 0101 = 0111
_

一方の入力でビットが0の場合、もう一方の入力からビットを取得します。 _(age << 8)_、_(gender << 7)_、およびheightを見ると、これらの1つのビットが1の場合、他のビットは0であることがわかります。そう:

_packed_info = (age << 8) | (gender << 7) | height = AAAAAAAGHHHHHHH
_

次に、ビットを解凍します。高さから始めましょう。最後の7ビットを取得し、最初の8ビットは無視します。これを行うには、_&_演算子を使用します。この演算子は、両方の入力ビットが1の場合にのみ1を返します。

_0011 & 0101 = 0001
_

そう:

_packed_info          = AAAAAAAGHHHHHHH
0x7F                 = 000000001111111
(packed_info & 0x7F) = 00000000HHHHHHH = height
_

年齢を取得するには、8か所すべてを右にプッシュするだけで、_0000000AAAAAAAA_が残ります。つまり、age = (packed_info >> 8)です。

最後に、性別を取得するために、高さを取り除くためにすべてを7か所右にプッシュします。次に、最後のビットのみを考慮します。

_packed_info            = AAAAAAAGHHHHHHH
(packed_info >> 7)     = 0000000AAAAAAAG
1                      = 000000000000001
(packed_info >> 7) & 1 = 00000000000000G
_
61
thomson_matt

これはビット操作のかなり長いレッスンになる可能性がありますが、最初に Wikipediaのビットマスキング記事 も指摘しておきます。

packed_info = (age << 8) | (gender << 7) | height;

年齢を取り、その値を8ビット以上に移動し、次に性別を取り、7ビット以上に移動すると、高さが最後のビットを占有します。

age    = 0b101
gender = 0b1
height = 0b1100
packed_info = 0b10100000000
            | 0b00010000000
            | 0b00000001100
/* which is */
packed_info = 0b10110001100

アンパックは逆を行いますが、0x7F(0b 01111111)などのマスクを使用して、フィールド内の他の値を削除します。

gender = (packed_info >> 7) & 1;

次のように動作します...

gender = 0b1011 /* shifted 7 here but still has age on the other side */
       & 0b0001
/* which is */
gender = 0b1

1へのAND演算はそのビットを「保持」することと同じであり、0とのAND演算はそのビットを「無視」することと同じであることに注意してください。

10
Andrew White

日付を数値として保存する場合は、年に10000を掛け、月に100を掛け、日を追加することでそれを実現できます。 2011年7月2日などの日付は、20110702という数値としてエンコードされます。

    year * 10000 + month * 100 + day -> yyyymmdd
    2011 * 10000 + 7 * 100 + 2 -> 20110702

yyyymmddマスクで日付をエンコードしたと言えます。この操作は次のように説明できます

  • 4年目を左にシフトし、
  • 月2の位置を左にシフトし、
  • その日はそのままにしておきます。
  • 次に、3つの値を組み合わせます。

これは、年齢、性別、身長のエンコーディングで起こっていることと同じですが、作者がバイナリで考えているだけです。

これらの値の範囲を確認します。

    age: 0 to 127 years
    gender: M or F
    height: 0 to 127 inches

これらの値をバイナリに変換すると、次のようになります。

    age: 0 to 1111111b (7 binary digits, or bits)
    gender: 0 or 1 (1 bit)
    height: 0 to 1111111b (7 bits also)

これを念頭に置いて、年齢-性別-身長データをマスクaaaaaaaghhhhhhhでエンコードできますが、ここではbinary数字、10進桁ではありません。

そう、

  • 年齢を8 bits左にシフトし、
  • 性別7 ビットを左にシフトし、
  • 高さはそのままにします。
  • 次に、3つの値をすべて組み合わせます。

バイナリでは、Shift-Left演算子(<<)は値nの位置を左に移動します。 「Or」演算子(多くの言語では「|」)は値を結合します。したがって:

    (age << 8) | (gender << 7) | height

では、これらの値を「デコード」する方法を教えてください。

10進数よりも2進数の方が簡単です。

  • あなたは高さを「隠す」
  • 性別を7ビット右にシフトし、それもマスクして、最後に
  • エイジを8ビット右にシフトします。

Shift-Right演算子(>>)は、値をn桁右に移動します(右端の位置からシフトアウトされた桁は失われます)。 「And」バイナリ演算子(多くの言語では「&」)はビットをマスクします。そのためには、保持するビットと破棄するビットを示すマスクが必要です(1ビットが保持されます)。したがって:

    height = value & 1111111b (preserve the 7 rightmost bits)
    gender = (value >> 1) & 1 (preserve just one bit)
    age = (value >> 8)

16進数の1111111bはほとんどの言語で0x7fであるため、それがマジックナンバーの理由です。 127(10進数では1111111b)を使用しても同じ効果があります。

5
Branco Medeiros

より簡潔な答え:

AAAAAAA G HHHHHHH

梱包:

packed = age << 8 | gender << 7 | height

または、MySQL SUM集計関数で使用する場合、コンポーネントを合計することもできます。

packed = age << 8 + gender << 7 + height

開梱:

age = packed >> 8 // no mask required
gender = packed >> 7 & ((1 << 1) - 1) // applying mask (for gender it is just 1)
height = packed & ((1 << 7) - 1) // applying mask


別の(長い)例:

パックしたいIPアドレスがあるとしますが、これは架空のIPアドレス(例:132.513.151.319)です。実際のIPアドレスとは異なり、256より大きいコンポーネントの中には、8ビット以上が必要なものがあることに注意してください。

最初に、最大数を保存するために使用する必要があるオフセットを把握する必要があります。私たちの架空のIPでは、コンポーネントが999を超えることはできません。つまり、コンポーネントごとに10ビットのストレージが必要です(最大1014までの数値を許可)。

packed = (comp1 << 0 * 10) | (comp1 << 1 * 10) | (comp1 << 2 * 10) | (comp1 << 3 * 10)

dec 342682502276またはbin 100111111001001011110000000010010000100

値を展開しましょう

comp1 = (packed >> 0 * 10) & ((1 << 10) - 1) // 132
comp2 = (packed >> 1 * 10) & ((1 << 10) - 1) // 513
comp3 = (packed >> 2 * 10) & ((1 << 10) - 1) // 151
comp4 = (packed >> 3 * 10) & ((1 << 10) - 1) // 319

どこ (1 << 10) - 1は、関心のある最も右側の10ビットより左側のビットを非表示にするために使用するバイナリマスクです。

MySQLクエリを使用した同じ例

SELECT

(@offset := 10) AS `No of bits required for each component`,
(@packed := (132 << 0 * @offset) | 
            (513 << 1 * @offset) | 
            (151 << 2 * @offset) | 
            (319 << 3 * @offset)) AS `Packed value (132.513.151.319)`,

BIN(@packed) AS `Packed value (bin)`,

(@packed >> 0 * @offset) & ((1 << @offset) - 1) `Component 1`,
(@packed >> 1 * @offset) & ((1 << @offset) - 1) `Component 2`,
(@packed >> 2 * @offset) & ((1 << @offset) - 1) `Component 3`,
(@packed >> 3 * @offset) & ((1 << @offset) - 1) `Component 4`;
3
Alexei Tenitski

左シフト演算子は、「2倍、これだけの回数」を意味します。バイナリでは、数値に2を掛けることは、右側にゼロを追加することと同じです。

右シフト演算子は、左シフト演算子の逆です。

パイプ演算子は「または」であり、2つの2進数を重ねて重ねることを意味し、いずれかの数値に1がある場合、その列の結果は1になります。

それでは、packed_infoの操作を抽出してみましょう。

// Create age, shifted left 8 times:
//     AAAAAAA00000000
age_shifted = age << 8;

// Create gender, shifted left 7 times:
//     0000000G0000000
gender_shifted = gender << 7;

// "Or" them all together:
//     AAAAAAA00000000
//     0000000G0000000
//     00000000HHHHHHH
//     ---------------
//     AAAAAAAGHHHHHHH
packed_info = age_shifted | gender_shifted | height;

そして、解凍はその逆です。

// Grab the lowest 7 bits:
//     AAAAAAAGHHHHHHH &
//     000000001111111 =
//     00000000HHHHHHH
height = packed_info & 0x7F;

// right shift the 'height' bits into the bit bucket, and grab the lowest 1 bit:
//     AAAAAAAGHHHHHHH 
//   >> 7 
//     0000000AAAAAAAG &
//     000000000000001 =
//     00000000000000G
gender = (packed_info >> 7) & 1;

// right shift the 'height' and 'gender' bits into the bit bucket, and grab the result:
//     AAAAAAAGHHHHHHH 
//   >> 8
//     00000000AAAAAAA
age    = (packed_info >> 8);
2
Anarchofascist

私が何度も直面した同じ要件。ビット単位のAND演算子を使用すると、非常に簡単です。 2の累乗で値を修飾します。複数の値を格納するには、それらの相対数(2の累乗)を加算して、SUMを取得します。このSUMは、選択した値を統合します。どうやって ?

すべての値でビットごとのANDを実行すると、選択されなかった値と選択されたゼロ以外の値にゼロ(0)が返されます。

ここに説明があります:

1)値(YES、NO、MAYBE)

2)2のべき乗への代入(2)

YES   =    2^0    =    1    =    00000001
NO    =    2^1    =    2    = 00000010
MAYBE =    2^2    =    4    = 00000100

3)YESを選択し、したがってSUMを選択します。

SUM    =    1    +    4    =    5

SUM    =    00000001    +    00000100    =    00000101 

この値はYESとMAYBEの両方を格納します。どうやって?

1    &    5    =    1    ( non zero )

2    &    5    =    0    ( zero )

4    &    5    =    4    ( non zero )

したがって、SUMは

1    =    2^0    =    YES
4    =    2^2    =    MAYBE.

詳細な説明と実装については、私の ブログ にアクセスしてください

1
user5212481

x & maskは、xに存在しない(つまり、値が0である)ビットをmaskから削除する操作として見ることができます。つまり、packed_info & 0x7Fpacked_infoから7番目のビットより上にあるすべてのビットを削除します。

例:packed_infoがバイナリで1110010100101010の場合、packed_info & 0x7f

1110010100101010
0000000001111111
----------------
0000000000101010

したがって、heightでは、packed_infoの下位7ビットを取得します。

次に、packed_info全体を7だけシフトします。これにより、すでに読み取った情報を削除します。したがって、(前の例の値の場合)111001010を取得します。性別は次のビットに格納されるため、同じトリックを使用します:& 1情報からそのビットのみを抽出しています。残りの情報はオフセット8に含まれています。

パッキングバックも複雑ではありません。ageを取得し、8ビットシフトして(1110010100000000から11100101を取得して)、genderを7シフトします(つまり00000000)を取得し、高さを取得します(それが下位7ビットに収まると想定)。次に、それらすべてを一緒に構成します。

1110010100000000
0000000000000000
0000000000101010
----------------
1110010100101010
1
Vlad