C言語ワークショップ 第7回  論理演算子とビット演算子

 演算子の中でも論理的演算となるビット演算子についてより深く解説します。

●論理演算子とビット演算子

 ビット演算とは、ある値を0,1で表される2進数とみなしそれら同士の値をある法則に基づき計算をすることです

 この論理演算は普通の加減算と異なるいくつかの定義があり、まずは概念の元となる一般的な論理演算について解説します。

●論理演算の概念

 コンピュータやデジタル回路など、0と1で表現するシステムにおいて次の概念が定義されています。

1.論理和(OR)

 論理和は理論的な加算を意味し0と1のみ表現される値にこの演算を次のように行います。
出力(結果)
0+0
0+1
1+0
1+1

 このように論理和は数値の論理加算により0、1が出力されます。当然1+1でも2にはなりません。論理演算は0と1であり2という状態になることはありえないからです。

 5Vで動作するデジタル回路で考えて頂くとわかりやすいと思います。たとえば回路において0と1をそれぞれ0Vと5Vで表現するシステムがあります。
 その場合入力に5Vと5Vが入ってきた時の論理和をとると、出力は5Vになります(電源が5Vなのに10Vが出ることはあり得ませんよね)

 結果的に論理和ではどちらか片方が1となれば出力1となる論理演算です。

2.論理積(AND)

 論理積は理論的な乗算を意味しその演算は次のように行なります。

出力(結果)
0×0
0×1
1×0
1×1

 これは普通にかけ算しても同じ結果になりますね。結果的には論理積ではどちらか両方が1となったときに出力1となる論理演算です。

3.論理反転(否定(NOT))

 論理反転はその名の通りです。入ってきた論理値が逆転します。

出力(結果)

4.排他的論理和(XOR)

 排他的論理和は理論的な剰余算に相当し、意味しその演算は次のように行なります。

出力(結果)
0 XOR 0
0 XOR 1
1 XOR 0
1 XOR 1

 0 XOR 0は0÷0の余りのように見なすことが出来、出力は0です。あくまで論理演算であり通常の数学では0で割ることが出来ませんが、論理演算においてはこうなります。

 0 XOR 1は0とならず1になります。これも2つの値の余りと見なすことが出来ます。1 XOR 0についても同様の値を出力します。

 1 XOR 1も1÷1も余りと見なすことが出来ます。

   結果的には演算元の論理値が同じで無ければ1、同じであれば0が出力されていると考えることも出来ます。

●C言語における論理値

 論理値を0と1で説明しましたが、Cにおいても同じくこの2値を表現する方法があります。すでに出てきた「真と偽」もその一つです。

 再度確認すると

TRUE0でない値
FALSE

 となります。 この真、偽で表現する方法をブーリアン代数(ブール値)とも呼び、その場合TRUE、FALSEと定数では表します。

●論理演算子

 さてこれらをふまえて、Cの論理演算子について解説します。

<論理和(論理OR )演算>

 論理ORは2つのブール値(真と偽)で論理和をとり、演算式は次の様な表記になります

 op1||op2

 そして、その演算結果は次の表のようになります。

条件結果
FALSE || FALSEFALSE
FALSE || TURETURE
TURE || FALSETURE
TURE || TURETURE

 この表から分かるように条件結果ははじめに説明した論理和演算と同様です。表を0,1表記からFALSE、TUREの真偽表記に書き換えたものと変わりません。

 ここで重要なのはやはり、真は0以外ということにあります

 kekka = (0 || 100);

 という論理OR演算をとると結果はどうなるのでしょうか
 答えから言うとkekkaには1が代入されます。 0以外の値を論理演算(ブール代数として)に使用した場合、すべてTUREですので

 kekka = (FALSE || TURE);

 と見なされます。そして、結果として代入される値ですが、0以外だから何でもいいって感じだとちょっと困りますし、ランダムなんとことはなくちゃんと決められており真偽値はFALSE=0、TURE=1と決まっています。そのためこの場合結果にはTURE=1が代入されることになります。

<論理積(論理AND)演算>

 論理ANDは2つのブール値(真と偽)で論理積をとり、その演算子は次のようになります

 op1&&op2

 演算結果は次の表のようになります。

条件結果
FALSE && FALSEFALSE
FALSE && TUREFALSE
TURE && FALSEFALSE
TURE && TURETURE

 これらの論理演算子を実際に使うときの例として具体的に説明しますと。

 if( a==1 && b==2 ){

 }

 等価演算子 == は論理演算子よりも優先順位が高いので先に評価されます。もしここで a==1、b==2 がどちらも正しければこの式は

 if( TURE && TURE ){

 }

 と置き換えられます。そして最終的にこの条件式は

 if( TURE ){

 }

 となりますので、if文が実行されるというわけです。

<論理反転(否定(NOT))演算>

 論理演算の3つ目は反転(否定)です。2つのブール値(真と偽)が逆転します、その演算子は次のようになります

 !op1

 op1の値をブール値と見なしその反転(反対の)ブール値を返します。他の2つの演算子と違い同じ論理演算子ですが、単項の演算子で、優先順位も他の2つより高くなります。

条件結果
!FALSETURE
!TUREFALSE

 if( !(a==1) && b==2 ){

 }

 このばあい、aが1でなくbが2である場合にif文が実行されることになります。

ちなみに

 !(100)

という表記であっても100はTUREであり、その反転となりますので、FALSE(=0)になります。

●ビット演算子

 ここまで、真偽値を扱う論理演算子について解説しましたが、今度は変数にある実際の値をビット列とみなし、そのビット列同士を論理演算の概念通りに演算するビット演算子を説明します。

<ビット分解>

 まずある値をビット列に分解して考えます。まずは10進数、16進数などの数値表記を2進数に置き換えます。

10進数16進数2進数
00x00000b
10x10001b
20x20010b
30x30011b
40x40100b
50x50101b
60x60110b
70x70111b
80x81000b
90x91001b
100xa1010b
110xb1011b
120xc1100b
130xd1101b
140xe1110b
150xf1111b

 よく見る表だとは思いますが、一応一覧を出します。

 この表通りに、変数にある値がそれに一致する2進数として見なされます。ビット演算子はこの表で表される2進数のビット列に対してそれぞれの演算が行われることになります。

<ビットOR演算子>

 ビットOR演算子はビット分解されたビット列の同じ桁のビット同士に対して論理和(OR)演算を行います。その表記は次のようになります。

 op1 | op2

 縦線は1本です。2本だと論理演算子になってしまいます。

 具体的に演算させると次のようになります

 a = 10;
 b = 5;
 c= a | b;

 さて、このときa、bそれぞれをビット分解してみましょう。

 aは 1010b
 bは 0101b

ですのでこの場合それぞれのビットの論理和(OR)をとると

  1010
  0101
 −−−−
  1111

 のように、cには1111b=15の値が代入されます。

 ビットORはこのように各ビットの同じ桁同士が論理演算された結果が得られます。

 具体的な利用法は様々な物があり、時と場合によって使い分けることもあるため、これという物は無いのですが、簡単な例だと

  1010
  1100
 −−−−
  1110

 このようにbの値の上位2桁のビットを1の値にしておくことでaの値が何であれ任意のビットを1にしてしまうことができます。これ単体ですとあまり有効では無いのですが、実際にはこの後説明するビット演算と組み合わせていくといろいろなビット合成が行えます

<ビットAND演算子>

 ビットAND演算子は各ビットに対して論理積(AND)演算を行います。

 op1 & op2

これも同様に&は1つです。

 ビットOR演算と同様の値でビットAND演算を行うと

  1010
  0101
 −−−−
  0000

 のように、cには0000b=0の値が代入されます。

 ビットAND演算は別名ビットマスク(ANDマスク)とも呼ばれ任意のビットを隠し、必要なビットだけを取り出すことが出来ます

  1010
  1100
 −−−−
  1000

 このようにbの値の上位2桁のビットを1の値にしておくことでaの値が何であれ上位2ビットのみのビットパターンが出力されます。
このことから

  1010
  0100
 −−−−
  0000

  1110
  0100
 −−−−
  0100

 のように、0100という値をマスク値とすると下から3ビット目が1か0かのみを判定すると行ったことが可能なであり、3ビット目がが0なら0000b(FALSE)が結果として得られ、1なら0100b(TRUE)が得られる訳です。

<ビットXOR演算子>

 ビットXOR演算子は各ビットに対して排他的論理和(XOR)演算を行います。

 op1 ^ op2

 次の値でビットXOR演算を行うと

  1011
  0101
 −−−−
  1110

 のように、cには1110b=0の値が代入されます。

 このXOR演算はC言語上だと頻繁でもないのですが、アセンブラではかなり頻繁に使います。  この演算子はいくつか特徴的な作用があり、たとえば

  1011
  1011
 −−−−
  0000

 同じ物値でXORをとると必ず0になります。この特性を利用して、
XOR A : A xor A → A

 のようにアセンブラにおいてレジスタの値を自分自身のレジスタの値でXORをとり、0クリアする等といったことに利用していました。

 また、引き算等でも可能なことですが、同じ値でのXORは0になるため、2つの値のXORをとり0であれば値が一致したという判定に使ったりできます。

 また

 a= 01101011
 b= 10110110
 −−−−−−−
 c= 11011101

 とした物を再度

 c= 11011101
 b= 10110110
 −−−−−−−
 d= 01101011

 といたように、bの値をキーにして2回XOR演算を行うと元の値に戻る(a==d)といったような特徴を持っています。

 これらの特徴を利用してデータエラーチェックのCRCや、エラー訂正などはXORを使った論理的除算を行い、データが割り切れるかどうかによってエラー判定や訂正を行っています。

<ビット反転演算子>

 ビット反転演算子は各ビットに対して論理反転(NOT)演算を行います

 ~op1

 ビット反転演算子は単項演算子になります。

 ~1010
 −−−−
  0101

 のように、cにはビットパターンが反転した0101b=0の値が代入されます。