論理演算

みなさん、はじめまして。とむとむと申します。
執筆時点では 技処 に所属している、自他ともに認める技術屋さんです。今回ミニミニセミナーの担当ということで、ブログにも出張してきました。
技術屋として短時間に語ることができる題材として、論理演算を取り上げてみました。実際には、論理演算は深く面白い題材で、短時間ですべてを語ることはできません。また、すべてを私が理解しているわけでもありません。そこで、新人が入ってきて間もない今、論理演算の入り口を、復習を兼ねて説明してみたいと思います。

論理演算とは?

真と偽の二通りの値(真理値)の演算のことです。アプリケーション開発においては、条件式に良く登場します。

真理値は、bool型とかBoolean型(ブール型)として定義されることが多いです。主に、関係演算子の結果が真理値となります。この場合は、値はtrueやfalseです。
言語によっては、数値として定義されます。この場合、0がfalseを表し、0以外がtrueとなります
ただし、trueが「0以外」だと定数として定義できないため、trueを1としている言語(多数)と、-1としている言語(BASIC系)があります。
身近なものではスイッチとみなせます。ONとOFFです。
スイッチ

関係演算子は比較演算子とも呼ばれます。つまり、何か二つのものを比較する演算子です。
例えば、「変数xが変数yより小さい」は、「x < y」と記述し、この条件が成り立てばtrue、成り立たなければfalseとなり、結果は真理値となります。

関係演算一つですべて判断できるほど、世の中あまくはありません。たとえば、「年齢が18以上」で「所持金がチケット代金以上」でないと、R18+指定の映画を観られないのです。
両方の条件が成り立って、初めて有効となります。このように、真理値の組み合わせなどを演算するのが論理演算となります。
基本演算子として、NOT(否定)、OR(論理和)、AND(論理積)があります。他の演算子もありますが、NOT、OR、ANDですべて表現可能です。

論理演算の結果は、色々な表現があります。特に「真理値表」が良く使用されます。真理値表は、入力に対する論理演算の結果を端的に表しており、今回この記事でも使用します。カルノー図(詳細はググってください……)のベースにもなっているので、覚えて損はありません。

プログラミング言語においては、「論理演算」と「ビットごとの論理演算」を分けているものがあります。ビットごとの論理演算を「ビット演算」とも呼びます。
ビットごとではない論理演算は、その対象の値を真理値としてみなしているだけです。
論理演算としての動作は同一ですが、結果の数値が異なったり短絡評価(ショートサーキット評価)になったりします。
ビット演算も条件式として使えますが、主として通信や画像処理、フラグ処理に使用されます。論理演算を「面白い」と思えるのは、実はビット演算です。実にコンピューターらしい処理を行えますが、今回は軽く触れるに留めます。

余談1)プログラム言語において、実は論理演算は必須ではありません。条件分岐の方法(主にif文)で、代用できます(できない言語もあります)。
しかし、読みやすく効率的な処理を書くためには、あった方が便利です。

余談2)プログラムの世界以外で論理演算が良く使われるのは、論理回路です。論理回路は、論理演算を行う電子回路のことです。
つまり、論理演算を行う物理的な部品のことで、CPUをはじめとする各種電子機器で使用されており、電子機器の基礎の基礎です。
CPUは処理を行うとき、論理演算を利用しまくっています。つまり、コンピューターは論理演算を得意としています。

NOT(否定)

not
NOTは、ある値1つに対して作用します(単項演算子と言います)。
NOTは、英単語と同じ意味合いで、否定です。真理値において否定とは、trueはfalseに、falseはtrueになるということです。
別の言い方をすると、反転と言えます。
条件式例としては、「入力された値が1ではない場合」などです。

 

if文においては、ある条件のelseブロックに処理を記述すれば、同様の効果が得られます。

ビット演算の場合、各ビットを反転することになります。つまり、4ビットの十進数「15」は二進数表記において「1111」となりますが、このNOTを取るとすべてのビットが反転されて「0000」となり、十進数での「0」となります。
詳細は省きますが、二進数のすべてのビットを反転させると「1の補数」を得られ、「1の補数」に1を加算すると、「2の補数」が得られます。
「2の補数」は、有限桁の二進数演算において負数とみなせます。4ビットの二進数表記「1111」は、十進数の「-1」とみなせます。

trueはfalseを反転した値です。BASIC系の言語は、純粋に数値0のNOTの値である数値-1をtrueと定義しています。
C言語などは、真理値表に則してtrueを1と定義しています。

現実世界で考えた場合、警告ランプでしょうか。正しく動作している場合、警告ランプは消灯しています。
動作が停止した場合、警告ランプは点灯します。

OR(論理和)

or
ORは、2つ(以上)の値に対して作用します。
ORも、英単語と同じ意味合いで、「または」です。入力値のうち、どれかがtrueなら結果もtrueとなります。
もちろん、すべての値がtrueであってもtrueです。すべての値がfalseの時のみ、結果はfalseとなります。
条件式例としては、「入力された値が1か、または2の場合」などです。

 

if文においては、条件1つごとに一つのif文を記述し、thenブロックにフラグを立てて、最後にフラグのthenブロックに処理を記述すれば、同様の効果が得られます。が、どう考えても面倒ですね……。

ビット演算の場合、各ビットに対して真理値表の計算をするのと同じです。
真理値表の結果がたし算に近いのですが、両方の入力値が1であっても、結果は2ではなく1です。つまり、1より大きくならないたし算になります。
「0000」に「1111」をORすると、「1111」になります。「1111」に「1111」をORしても「1111」のままです。
元の値がいくつであろうとも、1をORすると1になるということになります。
これは、ある値のビットを立てたい場合、その値に該当のビットを立てた値をORすれば良いことになります。

論理演算子(OrElseや||)で条件を記載した場合、ショートサーキット(短絡回路)で動作します。
ORはどれか1つがtrueなら結果はtrue確定ですので、AがtrueならBを評価しなくても結果はtrueです。この場合、無駄な処理であるBの評価を行わなくなります。

現実世界の場合、バスの降車ボタンが良く例に挙げられます。複数のボタンがありますが、どれか1つのボタンが押されたら、それでONとなります。

AND(論理積)

and
ANDは、2つ(以上)の値に対して作用します。
ANDも、英単語と同じ意味合いで、「かつ」です。入力値のすべてがtrueの場合、結果もtrueとなります。
入力値のどれか一つでもfalseがあれば、結果はfalseとなります。
条件式例としては、「入力された月が2で、かつ、入力された日が29の場合」などです。

 

 

if文においては、if文を入れ子にすることで同様の効果を得られます。

ビット演算の場合、各ビットに対して真理値表の計算をするのと同じです。
真理値表の結果が掛け算そのままです。
「1111」に「0011」をANDすると、「0011」になります。「0000」に「0011」をANDすると「0000」のままです。
ANDする値が1ではないと、元の値がいくつであろうとも、0となります。
これは、ある値のビットを取得したい場合、そのビットだけ1になった値をANDすれば良いことになります(マスクと言います)。

論理演算子(AndAlsoや&&)で条件を記載した場合、ショートサーキット(短絡回路)で動作します。
ANDはどれか1つがfalseなら結果はfalse確定ですので、AがfalseならBを評価しなくても結果はfalseです。この場合、無駄な処理であるBの評価を行わなくなります。

現実世界の場合、承認の仕組みが似ています。責任者全員の承認が取れないと、承認されたことになりません。

XOR(排他的論理和)

xor
XORは、2つの値に対して作用します。
XORは単語ではなく、略語です。Exclusive ORを略して「XOR」「EOR」「Ex-OR」などと表記します。
入力値の値が異なっていればtrue、同じであればfalseとなります。非常に理解しづらいのですが、1桁の二進数のたし算の結果の1桁目の結果となります。真理値表の上から順に、0+0=0、1+0=1、0+1=1です。1+1の場合、二進数ですから繰り上がりが発生し、10となります。1桁目は0です。よって、1+1=0です。

 

実際、CPUのたし算は、XORを使用して実現されます(実際にXOR回路が使われるかどうかは別の話)。
回路図で書くと、以下のようになります。
xor+

 

 

この回路図は半加算器と呼ばれるもので、1桁の二進数の加算を行えます。
AND回路は繰り上がりを計算し、XORが一桁のたし算の結果を計算します。この回路をOR回路で結ぶと全加算器となります。

XORは、前出のNOT、OR、ANDを組み合わせて表現できます。その場合、何種類かの表現がありますが、
A XOR B = (A AND NOT B) OR (NOT A AND B)などとなります。毎回書くのは大変ですね。XOR演算子が準備されていることは、ありがたいことなのです。

条件式例としては、「日付Fromと日付Toのいずれかが入力されている場合」などです。
あまり条件式では出番がありません。

ビット演算として考えた場合、0とXORを取ると、元の値から変化せず、1とXORを取ると元の値が反転します。
つまり、特定のビットの反転に用いられます。NOTもビット反転になりますが、XORは反転するビットを指定できるのが強みとなります。

現実世界の場合、一番の例は、階段の照明スイッチです。階段の照明は、階段の上と下の両方にスイッチがあります。
最初は、上OFF下OFF照明OFFです。上でも下でもどちらかをONにすると、入力値である上下の値が異なるので、XORの結果照明はONになります。その後に上でも下でもどちらかのスイッチを反転させると、上下の値が同一となり、XORの結果照明はOFFになります。

その他の論理演算子

NOT、OR、AND、XORの4つの演算子は、たいていのプログラミング言語に備えられています。
しかし、他にも論理演算子はあります。それらを簡単に紹介します。これらはXORと同様に、NOT、OR、ANDを組み合わせて表現できます。
算術ライブラリなどで準備されているプログラミング言語もあります。

NAND(否定論理積)

ANDの否定になります。つまり、入力値が両方trueである場合のみ、結果がfalseとなります。
論理回路の世界では非常に良く利用されます。NAND回路は構造が比較的簡単で高速に動作することと、NANDだけでNOT、OR、ANDを表現できるからです。究極的には、NAND回路さえあれば他の論理回路は不要となるため、コストも下げられます。

NOR(否定論理和)

ORの否定になります。つまり、入力値に1つでもtrueがある場合、結果がfalseとなります。
NAND同様、NORだけでNOT、OR、ANDを表現できます。しかし、NANDほど構造は簡単ではないため、使用頻度はNANDに劣ると思われます。

INV(論理包含)

(NOT A) OR Bです。BASIC系言語には言語仕様として備えられている演算子ですが、まず出番はありません。

EQV(論理等価)

XORの否定になります。こちらもINVと同様、BASIC系言語には言語仕様として備えられている演算子ですが、まず出番はありません。

論理演算の特性を利用すると、意外なことが簡単にできたりします。そのためには、二進数(ビット)の知識やビット演算、論理演算そのものの知識を得たうえで、活用することが重要です。
また、更に勉強していくと、ド・モルガンの法則やカルノー図など、より論理演算を活用する方法が出てきます。
CPUは論理回路が大量に使用されています。論理演算を理解するということ=CPUを理解することと言っても過言ではありません。つまり、論理演算はコンピューターの基本の1つです。
業種によっては、論理演算を条件式以外で使うことはほぼ無いかもしれません。しかし、論理演算の知識をしっかり持っていると、意外なところで役立ちます。どんな時に役立つかは……そのうち、くっぽーさんあたりが説明してくれると思います!(チラッ