Crystalには、Intをバイナリレベルで操作するためのビット演算子があります。
Crystalには、ビットを左(<<)または右(>>)にシフトするシフト演算子があります。
<<)左シフト演算子(<<)は、ビットを左にシフトします。
シフトする値は左側に、シフトする桁数は右側に指定します。
0b0010 << 1
# => 0b0100
0b0010 << 2
# => 0b1000
数値が負の場合、代わりに右にシフトします。
0b0010 << -1
# => 0b0001
>>)右シフト演算子(>>)は、ビットを右にシフトします。
左シフト演算子と同様に、シフトする値は左側に、シフトする桁数は右側に指定します。
0b0100 >> 1
# => 0b0010
0b0100 >> 2
# => 0b0001
同じように、数値が負の場合、演算子は左にシフトします。
0b0100 >> -1
# => 0b1000
Crystalには、ビット単位の演算を行うための3つの二項演算子(&、|、^)と、~演算子があります。
&)二項AND演算子(&)は、2つの値に対してビット単位のANDを実行します。
1つ目の値の各ビットを、2つ目の値の同じ位置にあるビットと比較します。
両方のビットが1であれば、結果のビットは1になります。
それ以外の場合は0になります。
0b0011 & 0b1010
# => 0b0010
|)二項OR演算子(|)は、2つの値に対してビット単位のORを実行します。
これも、1つ目の値の各ビットを、2つ目の値の同じ位置にあるビットと比較します。
どちらかのビットが1であれば、結果のビットは1になります。
それ以外の場合は0になります。
0b0011 | 0b1010
# => 0b1011
^)二項XOR演算子(^)は、ビット単位のXORを実行します。
ビット単位のAND演算子やビット単位のOR演算子と同様に、1つ目の値の各ビットを、2つ目の値の同じ位置にあるビットと比較します。
それらのうち_1つだけ_が1であれば、結果のビットは1になります。
それ以外の場合は0です。
0b0011 ^ 0b1010
# => 0b1001
~)最後に、ビット単位のNOT演算子(~)は、値の各ビットを反転させます。
他の二項演算子とは異なり、これは単項演算子で、右側の値だけを対象に操作します。
~0b1110_0010
# => 0b0001_1101
友達が、大切な秘密を書いたメッセージを送ってきました。 他の人に簡単に読まれたくないということで、そのメッセージは一連のビット操作を使って暗号化されています。 このメッセージを復号するには、プログラムを書く必要があります。
メッセージを復号する最初のステップは、暗号化のときにシフトされたビットを、左にシフトして元に戻すことです。 何ビット左にシフトするかは、メッセージによって異なります。
値と、左にシフトするビット数を受け取るメソッドSecrets.shift_backを実装しましょう。
Secrets.shift_back(0b0001, 2)
# => 0b0100
次のステップは、特定のビットの値を取り出すためにビットマスクを適用することです。 マスクを適用するには、値とマスクの間でビット単位のAND演算を行います。
値を受け取り、マスクを適用するメソッドSecrets.apply_maskを実装しましょう。
Secrets.apply_mask(0b0110, 0b0101)
# => 0b0100
ビットを左にシフトすると、新しい0のビットがいくつか加わります。 これらの新しいビットのいくつかは、1に設定する必要があります。 ビット単位のORは、ほかのビットを保ったまま特定のビットを1に設定するのに役立ちます。
2つの値を受け取り、ビット単位のOR演算を行うメソッドSecrets.set_bitsを実装しましょう。
Secrets.set_bits(0b0110, 0b0101)
# => 0b111
暗号化の過程の一部では、取り決めた値とのXOR演算が行われます。 元の値に戻すには、暗号化された値をもう一度、取り決めた値とXORするだけで済みます。 ただし、ちょっとした誤解から、友達はXORを適用する前に、取り決めた値のビットを反転させてしまいました(つまり、取り決めた値にビット単位のNOTを適用しました)。
暗号化された値と取り決めた値を受け取り、元の値を計算するメソッドSecrets.reverse_xorを実装しましょう。
Secrets.reverse_xor(0b1100, 0b0101)
# => 0b(0110)