今となっては、もうあまり使われることがなくなったビット演算子、低級言語に於いてはきっとポインタと同等程度に基本的かつ重要な演算子だったと思います。
高級言語しか使ったことのないプログラマの中には、ビット演算子について全く知らない人の方が多くなったんじゃないでしょうか?
そこで、ビット演算子とその便利な使い方についてまとめてみます。
ビット演算子とは?
ビット演算子の便利な使い方!
・and
・xor
論理演算子の便利な使い方!
ビット演算子とは?
あるデータを2進数表記した際の各データをビットと呼び、そのビットに対して作用する演算子のことをビット演算子といいます。
よくわかりませんよね?
具体的に説明します。
まず、ビット演算子には、「or」「and」「xor」「シフト」が存在します。
それぞれ、「|」「&」[^]「>> または <<」と表記します。
「or」「and」「xor」は二項演算子、「シフト」は単項演算子になります。
ここで、二進数表記で
変数A=100100111001100(10進数だと18892)
変数B=001101110111011(10進数だと7099)
という変数についてビット演算子がどのように作用するのかをみていきます。
ビット演算子「or」
orは各ビットについて、どちらかが「1」なら「1」、それ以外は「0」を返す演算子です。
変数A | 変数B をすると、 10進表記 100100111001100 18892 | ) 001101110111011 7099 ------------------------------------------- 101101111111111 23551
となります。
同じ位にある各ビットを比較し、どちらかが「1」であれば、そのくらいを「1」としたものが結果になります。
ビット演算子「and」
andは各ビットについて、両方「1」なら「1」それ以外は「0」を返す演算子です。
変数A & 変数B をすると、 10進表記 100100111001100 18892 & ) 001101110111011 7099 ------------------------------------------- 000100110001000 2440
となります。
同じ位にある各ビットを比較し、両方が「1」であれば、そのくらいを「1」としたものが結果になります。
ビット演算子「xor」
xorは各ビットについて、片方が「1」もう片方が「0」の組み合わせのときに「1」、それ以外つまり両方「1」または両方「0」であれば「0」を返す演算子です。
変数A ^ 変数B をすると、 10進表記 100100111001100 18892 ^ ) 001101110111011 7099 ------------------------------------------- 101001001110111 21111
となります。
ビット演算子「シフト」
シフト演算子はビット配列をずらす演算子です。
>> 変数A 100100111001100 → 010010011100110
となります。
<< 変数A 100100111001100 → 001001110011000
となります。
ただし、シフトにはいくつか種類があり、ずらした際に空くビット位置に何を入れるか?(上記例では0埋めしています)が変わってきます。
ビット演算子の便利な使い方!
ビット演算子は上記で示したとおりですが、この中で、「and」と「xor」には面白い使い方があります。
・and演算子
これは、マスクやフィルタとして使用されることがあります。有名な例では、ネットマスクがあります(ネットマスクについて、ここでは解説を割愛します。リンク先をご覧ください)。
and演算子は両方のビットが「1」のときに「1」を返すため、特定の位置にあるビットが「1」かどうかを用意に抜き出すことができるのです。
これを利用すると、
例)いくつかの機能があって、ユーザー毎に使用できる機能が違うとき、ユーザーが使用できる機能を調べる。
といったことを実現する際に、
機能1の機能番号→1 機能2の機能番号→2 機能3の機能番号→4 機能4の機能番号→8
と2の階乗に当たる数値を機能を設定し、ユーザーにそのユーザーが使用できる機能について、上記の足し算の値を持たせます。
機能1と機能3が使えるユーザーなら、5(これをユーザーの機能番号とする)を持たせます。すると、
5&1=1 5&2=0 5&4=4 5&8=0
となるので、
ユーザーの機能番号&機能番号!=0ならその機能は使用可能!と判断することが出来ます。
このような使い方を一般にビットマスクと呼んでます。このビットマスクは上記例以外にも様々な場面で応用が利き、またビット演算は速度が速いので、上手くロジックに組み込むとシンプルで可読性が高く軽いプログラムを作成することが出来ます。
・xor演算子
xor演算子の面白い特徴は、2回xorすると元に戻ることです。これによって、簡易な暗号化に使用されることがあります。
先ほどの変数Aと変数Bのxorを見てみます。
100100111001100 ^ ) 001101110111011 ----------------------------------- 101001001110111
これにもう一度変数Bをxorしてみます。
101001001110111 ^ ) 001101110111011 ----------------------------------- 100100111001100
変数Aに戻っていることがわかります。これを利用すると、変数Bを秘密の値として考えることで、簡易な可逆暗号が出来ています。
暗号というには貧弱すぎるため一般にはこのままでは使用されませんが、少し工夫して暗号化強度を高めることで簡易暗号として実際に使用することはあります。
滅多に使うことはない技術ですが、知っておく方がいい知識です。
最後に、論理演算子の便利な使い方!
これは使いどころが難しく、多用しすぎるとコードの可読性を損なう恐れもあります。また、よく使われているのはシェルスクリプトくらいであり、言語によっては下記の記述ができないものもあります。
論理演算子は「AND」「OR」があります。それぞれ「&&」「||」で表現します。
ビット演算子と似ていますが、全くの別物です。
論理演算子はよく使っていると思います。が、あまり知られていない挙動があります。それは、論理演算子は不要な判定はしないことです。
条件1 && 条件2 とした場合、
条件1が偽であれば、プログラムは条件2を判定しないのです。
それは、条件1が偽であれば全体が偽であることが保障されるからです。
また、
条件1 || 条件2 とした場合で、
条件1が真であれば、これも条件2は判定されません。
この特性により
条件1 && 処理1 || 処理2
と記載すると、条件1が真のとき、処理1が実行され、条件1が偽のとき、処理2が実行されます。つまり、
if ( 条件1 ) { 処理1 } else { 処理2 }
が一行で書けてしまうのです。
シェルスクリプトの場合、「ディレクトリの存在をチェックし、ディレクトリがあればそこに移動、なければエラーメッセージを表示する。」というプログラムを
[ -d ./hogehoge ] && cd ./hogehoge || echo "./hogehogeが存在しません。"
と記述できます。
また、if文の置き換え的な使い方は状況や要件が限られますが、このような記述が出来る理由である論理演算子の特性-「不要な評価はしない」はよく理解し、その特性を考えてif文の条件式を書くようにすると、プログラム速度の向上が見込めます。