秘密

秘密

学習演習

はじめに

2進数の桁は、結局のところ、CPUやRAMのトランジスタが「オン」か「オフ」かという状態にそのまま対応しています。

こうした低レベルな操作は、くだけた言い方では「ビット操作」と呼ばれ、特にシステムプログラミング言語で重要です。

Juliaのような高水準言語では、こうした詳細のほとんどは通常抽象化されています。しかし、言語本体にはビット単位の演算が一通り用意されています。

注: REPLで人間が読める形の2進数の出力を見るには、以下の例のほぼすべてをbitstring()関数で包む必要があります。これは見た目が煩雑になるため、この関数の記述のほとんどは省いてあります。

ビットシフト演算

整数型は、符号付きでも符号なしでも、1と0の並びとして表すことができます。

julia> bitstring(UInt8(5))
"00000101"

ビットシフトは、指定した位置の分だけすべてを左または右に動かすだけです。UInt型では、片端からいくつかのビットがこぼれ落ち、もう片端は0で埋められます。

julia> ux::UInt8 = 5
5

julia> bitstring(ux)
"00000101"

julia> ux << 2 # left by 2
"00010100"

julia> ux >> 1 # right by 1
"00000010"

左シフトするたびに値は2倍になり、右シフトするたびに値は半分になります(切り捨てを伴います)。これは10進数で表すと、よりはっきりわかります。

julia> 3 << 2
12

julia> 24 >> 3
3

こうしたビットシフトは「正式な」算術よりもはるかに速いため、低レベルなコーディングではこの手法がとてもよく使われます。

符号付き整数の場合は、もう少し注意が必要です。

左シフトは比較的単純です。

julia> sx = Int8(5)
5

julia> sx # positive integer
"00000101"

julia> sx << 2
"00010100"

julia> -sx # negative integer
"11111011"

julia> -sx << 2
"11101100"

このように、正の符号付き整数の左シフトは、符号なし整数の場合と同じです。

負の値は2の補数の形で格納されるため、いちばん左のビットは1になります。左シフトでは問題ありませんが、右シフトのときは、いちばん左のビットをどう埋めればよいのでしょうか?

julia> sx >> 2 # simple for positive values!
"00000001"

julia> -sx # negative integer
"11111011"

julia> -sx >> 2 # pad with repeated sign bit
"11111110"

julia> -sx >>> 2 # pad with 0
"00111110"

>>演算子は算術シフトを行い、符号ビットをそのまま保ちます。

>>>演算子は論理シフトを行い、その数が符号なしであるかのように0で埋めます。

ビット論理演算

以前のコンセプトで、演算子&&(論理積)、||(論理和)、!(否定)は真偽値に対して使うことを学びました。

2つの整数のビットを比較する、同じ働きをする演算子として、&(ビット単位の論理積)、|(ビット単位の論理和)、~(チルダ、ビット単位の否定)があります。

julia> 0b1011 & 0b0010 # bit is 1 in both numbers
"00000010"

julia> 0b1011 | 0b0010 # bit is 1 in at least one number
"00001011"

julia> ~0b1011 # flip all bits
"11110100"

julia> xor(0b1011, 0b0010) # bit is 1 in exactly one number, not both
"00001001"

ここでのxor()は排他的論理和で、関数として使います。

説明

友達から、大切な秘密が書かれたメッセージが届きました。 他の人に簡単に読まれたくないので、そのメッセージは一連のビット操作によって暗号化されています。 このメッセージを復号するための関数を書く必要があります。

1. ビットをシフトして戻す

メッセージを復号する最初のステップは、暗号化でシフトされた分を、ビットを右にシフトし直して元に戻すことです。 このあとの復号のステップでは、左側から0が挿入されることを前提としています。

値とシフトする桁数を入力として受け取り、シフトを実行するshift_back()関数を実装しましょう。

shift_back(0b1001, 2) # => 0b0010

2. ビットを立てる

次に、1に設定する必要があるビットがあります。

値とマスクを入力として受け取り、値のビットを1に設定した結果を返すset_bits()関数を実装しましょう。 maskのビットが1になっている位置では、valueのビットも1に設定します。 それ以外のビットは変更しません。

set_bits(0b0110, 0b0101) # => 0b0111

3. 特定のビットを反転させる

暗号化のときに反転されるビットもあります。 メッセージを復号するには、それらをもう一度反転して元に戻す必要があります。

値とマスクを入力として受け取るflip_bits()関数を実装しましょう。 マスクは、値のどのビットを反転するかを示します。 maskのビットが1であれば、valueのビットを反転します。 それ以外のビットは変更しません。

flip_bits(0b1100, 0b0101) # => 0b1001

4. 特定のビットをクリアする

最後に、復号すると必ず0になるビットもあります。

値とマスクを入力として受け取るclear_bits()関数を実装しましょう。 マスクのビットが1になっている位置では、valueのビットを0に設定します。 それ以外のビットは変更しません。

clear_bits(0b0110, 0b0101) # => 0b0010
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Julia Exercism

秘密を始める準備はできましたか?

Exercismに登録すれば、35個のコンセプト128個の演習、そして本物の人間によるメンタリングとともに、Juliaを学んでマスターできます。すべて無料です。