低レベルのビット操作は私の強みではありませんでした。次のビット単位演算子の使用例を理解するのに役立ちます。検討してください...
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を使用する理由梱包と開梱はどのように行われますか?
コメントにあるように、年齢、性別、身長を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
_
これはビット操作のかなり長いレッスンになる可能性がありますが、最初に 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演算はそのビットを「無視」することと同じであることに注意してください。
日付を数値として保存する場合は、年に10000を掛け、月に100を掛け、日を追加することでそれを実現できます。 2011年7月2日などの日付は、20110702という数値としてエンコードされます。
year * 10000 + month * 100 + day -> yyyymmdd
2011 * 10000 + 7 * 100 + 2 -> 20110702
yyyymmddマスクで日付をエンコードしたと言えます。この操作は次のように説明できます
これは、年齢、性別、身長のエンコーディングで起こっていることと同じですが、作者がバイナリで考えているだけです。
これらの値の範囲を確認します。
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進桁ではありません。
そう、
バイナリでは、Shift-Left演算子(<<)は値nの位置を左に移動します。 「Or」演算子(多くの言語では「|」)は値を結合します。したがって:
(age << 8) | (gender << 7) | height
では、これらの値を「デコード」する方法を教えてください。
10進数よりも2進数の方が簡単です。
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)を使用しても同じ効果があります。
より簡潔な答え:
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`;
左シフト演算子は、「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);
私が何度も直面した同じ要件。ビット単位の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.
詳細な説明と実装については、私の ブログ にアクセスしてください
式x & mask
は、x
に存在しない(つまり、値が0である)ビットをmask
から削除する操作として見ることができます。つまり、packed_info & 0x7F
はpacked_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