プログラムの実行を制御しよう目指せ! Cプログラマ(7)(2/3 ページ)

» 2010年08月23日 10時00分 公開

等価演算子、関係演算子、論理演算子の注意点

 ここまで登場した演算子は、動作は単純なように見えます。しかし、実際にプログラムで使うときには、いくつか注意すべき点があります。プログラムを作るときに困らないよう、そんなこともあるということを気にとめておきましょう。

(1) 浮動小数点数の比較

 浮動小数点数を比較するときに等価演算子(“==”や“!=”)を使うと、思わぬ動作をすることがあります。次の例を見て下さい。

double a = 0.3 - 0.2;
double b = 0.1;
printf("a(%f) == b(%f) : %d", a, b, a == b);

 このコードはaとbを比較した結果を表示しています。出力は次のようになります。

a(0.100000) == b(0.100000) : 0

 最後の0は、aとbが等しくないことを表しています。これだけ見ると何が起こっているか分かりませんので、前述のコードを次のように変えてみて下さい。

double a = 0.3 - 0.2;
double b = 0.1;
printf("a(%.18f) == b(%.18f) : %d", a, b, a == b);

 1行目と2行目はまったく同じで、3行目の「%f」が「%.18f」になっています。これは小数点以下18桁を表示するという意味です。先のコードでは途中の桁までしか表示していないため、もっと下の桁まで表示させます。これを実行すると次のようになります。

a(0.099999999999999978) == b(0.100000000000000010) : 0

 これであれば、確かにaとbが等しくないことは分かります。しかし、aもbも0.1のはずですが、なぜ異なる値になるのでしょうか。

 これはコンピュータが、浮動小数点数も2進数で扱っているのが原因です。2進数では、10進数の0.1という値を有限桁で正確に表せないのです。詳しい解説は別の機会に譲りますが、ここではこのような問題があるということを覚えておいて下さい。

 では、このようなときにはどうすればいいでしょうか。対応方法は、どのようなプログラムが求められているかによって変わります。正確な精度が必要なのであれば、コンパイラの仕様をよく確認して、正確な計算が行われる方法を見つけて下さい。

 それほどの精度が必要ないのであれば、許される範囲で誤差を認めてしまう方法があります。例えば、変数xの値が0.1かどうか調べるときに0.001の誤差の範囲であれば正しいとするならば、「x == 0.1」ではなく、「(0.1 - 0.001) < x && x < (0.1 + 0.001)」という条件にしてみましょう。あるいは、「(x - 0.1) < 0.001」のようにも書けます。

(2) a < b < c

 ここで、「(0.1 - 0.001) < x && x < (0.1 + 0.001)」という条件式が出てきましたが、xが2回現れるのが気に入らない方はいませんか。算数や数学でよく使うように「(0.1 - 0.001) < x < (0.1 + 0.001)」と書けないのでしょうか。

 残念ながらこのような書き方は思ったように動きません。文法違反ではないのでコンパイルして実行することはできますが、期待した結果にならないのです。

 この式で使われている関係演算子“<”は、同じ優先順位の演算子があったら左にある演算子が先に評価されます。これは第5回で説明した、演算子の結合規則によるものです。ほとんどの演算子は左にあるものが優先され、関係演算子もその1つです。

 左から演算されるということは、「(0.1 - 0.001) < x < (0.1 + 0.001)」のうち、「(0.1 - 0.001) < x」が先に演算されます。“<”の評価結果は先に説明したとおり、「左オペランドが右オペランドより小さければ1(真)、そうでなければ0(偽)」ですから、ここではxが0.1であるとすれば、「(0.1 - 0.001) < x」の結果は1(真)になります。

 となると、残りの式は「1 < (0.1 + 0.001)」になります。当然これは成り立たない式ですから、結果は0(偽)となってしまいます。

「(0.1 - 0.001) &lt; x &lt; (0.1 + 0.001)」はどうなる?

 比較を2回以上行いたい場合には、&&や||を使って式を組み合わせるようにしましょう。

(3) 論理演算子と等価、関係演算子の優先順位

 論理ANDや排他ORなどの演算を行う論理演算子は、第4回で紹介しました。これらの演算子と、今回紹介した等価演算子や関係演算子、さらに四則演算の演算子について、優先順位を確認してみましょう。表の上にあるものほど優先順位が高いということを表しています。

乗除演算子 * / %
加減演算子 + -
関係演算子 < > <= >=
等価演算子 == !=
AND演算子 &
排他OR演算子 ^
OR演算子 |
第4回の優先順位表から抜粋の優先順位表から抜粋

 これをみると、論理演算子よりも等価演算子や関係演算子の方が、さらに四則演算の演算子の方が優先順位が高いことが分かります。

 ということは、次のように評価されることになります。

  • 「x == y + z」では、「x」と「y + z」の演算結果が比較される
  • 「x == y & z」では、「x == y」の結果と「z」の論理ANDが計算される

 前者はよく現れる形なので、自然と+演算子の方が先に評価されるということは覚えやすいと思います。ところがそれが身についていると、よく似ている後者の形で、==演算子の方が先に計算されるということに気づきにくいことがあります。もしそのことを知らないと、いくらソースコードを眺めてもプログラムの問題が分からず、困ってしまうことになります。

 後者の式で「x」と「y & z」の結果を比較したいときには、優先順位を変えられる()を利用して、「x == (y & z)」としましょう。

ブロック(複合文)

 さて、もう一度ifの書き方を確認します。elseも含めたifの書き方は、次のようになっていました。

if (制御式) 文 else 文

 今度は「文」のところに注目してみます。この書き方を単純に考えると、「文」のところには1つの文しか書けないように見えます。文にはいくつか種類があり、代表的なものは式文です。これは「式;」の形をしたものです。

 文が1つしか書けないとなると、ちょっと不便です。2つ以上の文を書きたいときもあるでしょう。そんなときに“ブロック”を使います。ブロックでは、複数の文を1つの文のように記述することができます。

int ex = 10;
if (10 <= ex) {
  lv++;
  printf("Level Up\n");
}

 「{」で始まり、「}」で終わっている部分がブロックです。

 ここでは、変数exの値が10以上であれば、変数lvの値をインクリメント(+1)し、「Level Up」と表示します。この2つの式文を1つのブロックとすることで、ifの文として利用することができます。

 ブロックの中にはどのような文でも書くことができます。1つのブロックの中に、文がいくつあってもかまいません。1つもなくても問題ありません。

 ブロック(複合文)は文の一種ですので、文を書けるところにはブロックを書くことができます。今まで式文を書いていたところには、ブロックを書くこともできます。

 ブロックは入れ子にすることができます。つまり、ブロックの中にブロックを書くことができます。また、ブロックの中には文のほかに、宣言を書くこともできます。

 これらをまとめると、次のようなコードを書くことができます。

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
  int lv = 1;
  int ex = 10;
  int maxhp = 10;
  if (10 <= ex) {
    // ブロックの中には、文をいくつでも書くことができます。
    lv++;
    printf("Level Up\n");
    // ブロックの中には、ブロックも書くことができます。
    if (lv <= 3) {
      // ブロックの中に宣言を書くこともできます。
      int plushp = lv * 3;
      maxhp += plushp;
    }
  }
  printf("ex=%d , maxhp=%d\n", ex, maxhp);
  return EXIT_SUCCESS;
}

Index

プログラムの実行を制御しよう

Page1
if文(選択文)
制御式

Page2
等価演算子、関係演算子、論理演算子の注意点
ブロック(複合文)

Page3
繰返し文(while、do while、for)
分岐文(continue、break、goto)
switch文
今回学んだこと


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。