第23回 式と評価

今回は「式」と「式の評価」という考え方を習得して、while構文と比較演算子について理解を深めます。

比較「演算」子

前回の記事では「何かと何かが等しい」などの比較をする記号を習得しました。

  • ==
  • !=
  • <
  • <=
  • >
  • >=

これらは比較する記号です。意識高い系の場合、「記号」は「子」と呼ぶんでしたよね。ということは「比較する記号」は「比較子」ですよね。

ここで疑問が出てきてしまいました。「比較する記号」のことを「比較子」ではなく「比較演算子」と呼んでいます。つまり「比較を計算する記号」というわけですが、なぜ計算(演算)するのでしょうか。

今回の記事では、なぜ「演算」という言葉が使われているかという疑問から出発して、比較演算子とwhile構文について理解を深めていきます。

 

Arduinoの計算能力

ちょっと驚くかもしれませんが、Ardunoボードを始め、多くのコンピュータは数字の比較ができないんです。とはいっても、前回のスケッチで

while(digitalRead(SWITCH) == 1) {
}

と書いて、スイッチの状態である「digitalRead(SWITCH)」と「1」が等しいかどうか比較していましたよね。これ、どうみても比較です。

一般的なコンピュータ、つまり計算機は、当然ながら計算は得意なのですが、できることはかなり限られています。Arduinoの場合、計算と言ってもできることはこれぐらいなんです。

  • 整数の足し算
  • 整数の引き算
  • 計算結果が0か0以外か判定する
  • 計算結果がプラスかマイナスか判定する

これだけといっても、実際には比較とか、掛け算、割り算、さらには少数の計算をしているのですが、一体どうしてるんでしょうか。

実は、Arduinoボードの内部では、足し算、引き算、結果が0かマイナスかプラスかの判定だけで、スケッチに書かれた比較や掛け算割り算などの複雑な計算をやっているんです。

そこで今回の記事では、Arduinoボードが比較をどのように実現しているのか一緒に詳しくみていきましょう。

 

比較演算子の処理

これから以下のスケッチのArduinoボード内部処理のようすを詳しくみていきます。

digitalRead(SWITCH) == 1

これはスイッチの状態を調べて、その値が1に等しいかどうか調べる、という動作内容のスケッチです。Arduinoボードはこのスケッチをどのように処理しているか、順番にみていきましょう。

この比較をするには、最初にdigitalRead(SWITCH)を処理する必要があります。digitalRead(SWITCH)は以下のような値になるんでしたよね。

スイッチの状態 digitalRead(SWITCH)の値
OFF 1
ON 0

ここで、スイッチがOFFのケースを考えます。スイッチOFFの場合はdigitalRead(SWITCH)の値は1ですので、先ほどの比較のスケッチは「digitalRead(SWITCH)」の部分が「1」になるので、Arduinoボードは内部で以下のように変換します。

1 == 1

次に、Arduinoボードは「==」の左側の数字から右側の数字を引き算します。つまり「1 – 1」を計算します。結果はどうなるかというと「0」ですよね。Arduinoボードは結果が0の場合はこの比較が成立している、0以外の場合は比較が成立していない、と判定します。

このように、Arduino内部では比較を行う場合、引き算をしてその結果がどうなるかを判定しています。つまり「==」などの比較の記号は「比較するための計算の記号」つまり「比較演算子」だったわけです。

ここで説明した例は等しいケースですが、例えば「○ > □」の比較を行う場合、Arduinoボードは最初に「 ○ -
□」という引き算を行い、結果がプラスであれば成立している、プラスでなければ成立していない、と判断します。

他の比較の記号も全て、左側の数字から右側の数字を引いて、その結果がどうなるか判定しているわけです。これが「比較演算子」と呼ばれている背景です。比較の記号が「比較演算子」と呼ばれている理由はなんとなくわかりましたでしょうか。

ここまでが、Arduinoボードが比較演算子のスケッチが成立しているか、成立していないか判定するまでの処理です。

この先、さらにややこしくなります。

 

真と偽

先ほどの比較演算子のように、コンピュータ内部では、何かが成立しているか成立していないか、という判定が頻繁に行われます。

例えば比較演算子で、以下のような場合、

1 + 2 == 3

比較演算子の左側は3、右側も3ですので成立しています。また、以下のような場合、

2 + 3 == 8

比較演算子の左側は5、右側は8ですので、成立していません。

このように成立している状態、正しい状態を「真」(しん)と呼びます。英語では「true」(トゥルー)と呼んでいます。

また、成立していない状態、正しくない状態を「偽」(ぎ)と呼びます。英語では「false」(フォルス)と呼んでいます。

このように成立している状態、成立していない状態は、日本語を話す人であれば「真」か「偽」、英語を話す人であれば「true」「false」ですが、コンピュータ内部では数字で表しています。

なんともわかりづらいですが、C/C++言語に限らず、他のプログラミング言語でも、真は0以外の数字、偽は0で表すことになっています。真の場合は0以外の数字ですが、一般的には数字の1が利用されています。

これまでのことをまとめると、以下のようになります。

状態 日本語 英語 コンピュータ語
成立している
正しい
true 0以外の数字
(一般的には数字の1)
成立していない
正しくない
false 数字の0

Arduinoボードは比較演算子を処理するとき、計算をして計算結果を見て成立しているか成立していないか判定しました。Arduinoボードの内部ではその判定結果を真か偽かを意味する0か1かに置き換えています。

といてもさっぱりわからないですよね。それでは、Arduinoボードの中で、以下の比較がどのように行われているか、もう一度最初から順を追って見ていきましょう。

digitalRead(SWITCH) == 1

Arduinoボードは最初にdigitalRead(SWITCH)を処理します。この時、スイッチがOFF、つまりdigitalRead(SWITCH)の結果が1とします。Arduinoボードは上のスケッチを以下のように変えます。

1 == 1

これで比較ができるようになったので、Arduinoボードは「==」の左側の数字から右側の数字を引き算した結果を求めます。「1 – 1 = 0」ですので、結果は0です。「==」の場合、結果が0であれば成立している、0以外であれば成立していないという判定になりますので、この比較は計算結果が0ということで成立している、と判定します。

成立している、つまり真(true)ということがわかりましたので、Arduinoボードはこの比較を真を表す「1」に置き換えて処理が完了します。

1

まとめると、Arduinoボードの中では以下の比較を処理すると、

digitalRead(SWITCH) == 1

成立している場合は、

1

に変換する、というわけです。なんだかわかったようなわからないような、という感じですので、実際にArduinoボードの動きをシリアルモニタで確認してみましょう。

 

真か偽の判定

それでは、新規スケッチを作成してください。スケッチ名は「true_false」にしておきましょう。

このスケッチでは、比較演算子が最終的に「1」(真)か「0」(偽)のどちらかに変換される様子をシリアルモニタで確認します。

確認用に以下のスケッチを作成しました。

比較演算子の結果の表示スケッチ(真のケース)

/*
* 内容: 比較演算子の結果を表示する
* 変更履歴:
*   2019.8.17: 新規作成
*/

void setup() {
  Serial.begin(9600); // シリアルモニタの設定(通信速度は9600)
}

void loop() {
  // 1秒に1回比較演算の結果をシリアルモニタに表示する
  Serial.println( 1 + 2 == 3 );
  delay(1000);
}

この内容でスケッチを作成したら、ArduinoボードをPCに接続して、シリアルモニタウィンドウを開きます(ツール→シリアルモニタ)。準備ができたらスケッチをArduinoボードに送ります。

しばらく待っていると、以下のように「1 + 2 == 3」の結果が表示されます。

比較演算子の結果表示

Arduinoボードは、「1 + 2 == 3」を演算して、その結果真であると判定したため「1」(真)と表示しています。それでは、比較が成立しないように書き換えて、その結果がどうなるか確認してみてください。例えば「1 + 3 == 5」は成立しませんので、この結果を表示するとどうなるか確認してみてください。

成立しない場合は「0」(偽)が表示されますよね。

C/C++言語に限らず、プログラミングの世界では「真」は0以外の数字(一般的には数字の1)、「偽」は数字の0で表現する、ということは非常に重要ですので、十分理解しておきましょう。

 

「式」と「式の評価」

Arduinoボード内部では「digitalRead(SWITCH) == 1」にしても「1 + 2 == 3」にしても、左側から右側を引き算してその結果が0か0以外かでこの比較が成立するか判定していました。

ここで、新しい言葉を覚えておきましょう。

「digitalRead(SWITCH) == 1」や「1 + 2 == 3」などの計算や比較を「式」と呼びます。数学でも式が出てきますので特に違和感はないですよね。

またArduino内部では比較の式を、左側から右側を引き算してその結果が0か0以外かで成立するか判定しています。この判定する処理のことを「評価する」と呼びます。

言い方としては「『1 + 2 == 3』という式を評価すると結果は真(1)だ」などと言います。

特に「評価」というと、何かランク付けするイメージが強いかもしれませんが、プログラミングの世界では「評価」をこのような意味合いで使用しています。この言い方はC/C++言語に限らず、全てのプログラミング言語で共通です。

 

while構文の本当の意味

前回の記事で、while構文は以下のように書きます、と説明しました。

while(条件)
命令;

実はこの説明はあまり正確ではないんです。while構文は以下がより正確な説明になります。

while構文

while構文は、whileの括弧の中の式を評価した結果が「真」の間、「命令」を繰り返す、という動作をする構文なんです。

あまりおすすめできない書き方ですが、スイッチを押すとLEDの点滅を開始するスケッチは以下のようにも書けます。

while構文の真偽判定を利用した書き方

while構文では比較演算子ではなく、直接digitalRead(SWITCH)を書いています。digitalRead(SWITCH)は、スイッチがOFFの時は1、ONの時は0になります。つまり、スイッチOFFの時は「1」=「真」、スイッチONの時は「0」=「偽」になるため、わざわざ「digitalRead(SWITCH) == 1」と書いて、1と比較する必要はないわけです。

ただ、このようなスケッチはわかりづらいので、書かないようにしてください。

なお、他の人が書いたスケッチやC/C++言語で書かれたプログラムでは、この仕組みを利用した書き方をする場合もあります。while構文の仕組みを十分理解しておくようにしてください。

 

while構文をわかりやすく書く

while構文の説明の最後に、スケッチをわかりやすく変更します。

と言われても、どこがどうわかりにくいか、って感じですよね。まずわかりにくい背景を説明します。

わかりにくい部分はここです。

while(digitalRead(SWITCH) == 1){
}

何がどうわかりにくいのか、ちょっと???ですよね。今まで一緒にスケッチを作ってきましたので、私たちはこのスケッチの意味がわかっています。でも他の人が急にこのスイッチを見た場合を考えてみてください。「digitalRead(SWITCH)」という部分は、読み取っている端子はおそらくスイッチだろう、ということが想像できます。でも「1」ってなんなんでしょうか。スイッチがONなのかOFFなのか、直感的にわかりづらいですよね。

このように式の中に数字を直接書くとわかりづらいことが多々あります。そこで、スイッチONの時とスイッチOFFの時で、digitalRead(SWITCH)命令がどのような値になるのか、#defineで明確にしておきます。

#define SWITCH_OFF 1
#define SWITCH_ON 0

このように定義しておくと、先ほどのwhile構文は以下のように書けます。

while(digitalRead(SWITCH) == SWITCH_OFF){
}

while構文は、式が真の間繰り返す、という処理をしますので、これを初めてみた人は「スイッチの値を読んで、その値がスイッチOFFの間、何もしないんだな」ということが想像できます。

スケッチの中に数字を直接書く場合は、もう少しわかりやすく書ける方法があるか一度考えてみるようにしてください。

スイッチ関連の#define追加スケッチ

/*
* キッチンタイマー
*
* 内容: スイッチ、LED、スピーカーを使ったキッチンタイマー
* 変更履歴:
*   2019.8.11: 新規作成
*   2019.8.15: スタートスイッチ処理を追加
*   2019.8.17: スイッチ関連の#define追加
*/

// 秒を表現するLED関連(青色LED)
#define BYOU_LED 12 // 秒を表現する青色LEDの端子番号
#define BYOU_ON  50 // 秒を表現するLEDをつけている時間 (単位:ミリ秒)
#define BYOU_OFF 1000 - BYOU_ON // 秒を表現するLEDを消している時間 (単位:ミリ秒)

// スタートスイッチ関連
#define SWITCH 23    // スイッチを接続している端子番号
#define SWITCH_OFF 1 // スイッチOFFの時のdigitalReadの値
#define SWITCH_ON  0 // スイッチONの時のdigitalReadの値

void setup() {
  // 端子の設定
  pinMode(BYOU_LED, OUTPUT);  // 青色LED接続端子設定
  pinMode(SWITCH, INPUT_PULLUP); // スイッチ接続端子の設定

  // スイッチが押されるまで待つ
  while(digitalRead(SWITCH) == SWITCH_OFF) {
  }
}

void loop() {
  // 1秒に1回青色LEDを点滅する
  digitalWrite(BYOU_LED, HIGH);
  delay(BYOU_ON);
  digitalWrite(BYOU_LED, LOW);
  delay(BYOU_OFF);
}

 

【補足】ずっと繰り返す処理の書き方

電子工作のプログラミングでは、ずっと何かの処理を繰り返す、という動作をすることがいろいろな場面で出てきます。他の人が書いたスケッチでもずっと繰り返すという処理が出てきます。そのようなスケッチを読めるように、いくつか書き方を覚えておきましょう。

ずっと繰り返す、ということは、whileの式が常に真であればOKです。真は「1」ですので以下のように書くと「処理」をずっと繰り返します。

while(1){
 処理;
}

ただ、「1」と書くのは数字自体に意味があるのか単に真を表すために0以外の数字ということで「1」と書いてあるのかよくわかりませんよね。

実は、Arduino IDEではよく出てくる真と偽について、裏で以下のように定義してくれています。

#define true 1
#define false 0

「true」は「真」、「false」は「偽」の意味でしたよね。この#defineは見えないところで定義してくれているので、スケッチの中でいつでも使えます。

そのため、他の人のスケッチを見ると、ずっと繰り返す場合は以下の書き方をよく見かけます。

while(true){
 処理;
}

これでスタートスイッチの機能が実現できましたので、次回からいよいよ時間を数えるスケッチを作成します。

 

 

更新履歴

日付 内容
2019.8.17 新規投稿