こうならばああ・・・

もしこうなら・・・

日常生活でも「こうならああする」って場面はあるだろう。例えば「テストで赤点だった→追試を受ける」とか「告白された相手がT君だった→振る」ってな具合だ。Cだって何も最初から決められてとおり、実行するだけとは限らなく、「こういう場合は」ってな具合で条件をつけることが出来る。

1	#include <stdio.h>
2
3	int main(void)
4	{
5		int v1 = 3, v2 = 4;
6
7		if (v1 == v2)
8		{
9			printf("v1とv2は等しい値です。\n");
10		}
11		else
12		{
13			printf("v1とv2は異なる値です。\n");
14		}
15		if (v1 + v2 > 5)
16		{
17			printf("v1とv2の和は5より大きいようです。\n");
18		}
19
20		return 0;
21	}

L5でint型変数の"v1"、"v2"が確保されていてそれぞれ"3"と"4"で初期化してある。で、L7からL14、L15からL18は今回の本題でもある。構文としては次のような感じだ。

if ([条件式])
{
	[条件式が成立した場合に実行する内容]
}
else
{
	[条件式が非成立の場合に実行する内容]
}

ちなみに"else"以下は必要がなければ省略できる。また、[・・・実行する内容]が1ステートメントだけの場合はそれら前後の中括弧"{}"は省略できる。

で、もう一度プログラムを眺めてみるとL7の括弧の中は"v1 == v2"とある。これは"v1"と"v2"が等しければとの意味だ。よく見てみよう。"="ではなく、"=="であることに注目しよう。代入を意味する"="ではない。ちなみに間違って"="にしてもコンパイルエラーにはならないので要注意だ。その理由は・・・薀蓄は後回し(爆)。

で、条件が成立して"v1"と"v2"が等しい場合、"v1とv2は等しい値です。"と表示されるわけだ。条件が非成立で"v1"と"v2"が等しくない場合は"v1とv2は異なる値です。"と表示される。

L15のように不等号をいれることもできるんだ。この場合は"v1"と"v2"の和が5より大きい場合に実行されるって意味で、条件を満たせば"v1とv2の和は5より大きいようです。"と表示されることになる。

条件式

if文の括弧の中は条件式と書いたけど、具体的にどういうことなのよ、ってのがここのテーマ。さっきはは漠然と"v1"と"v2"が等しいときって書いたけど条件式ってのはif文以外にも出てくる話なので正確なイメージを抑えておこう。

「条件式」ってのはその名の通り、「式」なのである。「え、"v1"と"v2"が等しいか判断するのに式?」って気がするけどそういうわけで、これをまず、確認してみよう。

1	#include <stdio.h>
2
3	int main(void)
4	{
5		int v1 = 3, v2 = 4, result;
6
7		result = (v1 == v2);
8
9		printf("%d = (%d == %d)\n", result, v1, v2);
10
11		return 0;
12	}

で、実際に実行してみるとこんな感じになった。

0 = (3 == 4)

では、L5で"v1"と"v2"の初期値を両方とも"4"に変更してみた。で、実行。

1 = (4 == 4)

この結果はコンピュータや使っているコンパイラによっては違うかも知れない。"result"は"1"だとは限らないはず、ただ、"0"ではないはずである。「0でない」ってのをCでは非0といったりする。

これが条件式の全てだったりする。つまり、条件式が非0なら成立、0なら非成立ってことなのだ。

よって条件式ではこんなのもありだ。

  1. if (1)
  2. if (v1)
  3. if (v1 = v2)
  4. if (!v1)

1番目のやつは常に1、つまり非0なのでif文は常に成立するとして扱われる。「何のためのif文なの?」たしかにif文では意味がないけど後々、よく使う例が現れるので注目。2番目のやつは"v1"が"0"以外の場合に成立。3番目は"v2"を"v1"に代入、でその値が"0"以外なら成立。前の例で"="と"=="を間違えてもエラーにならない理由がわかっただろう。4番目の"!"は非0を0に、0を非0にの意味。だからv1が0だと成立する。

ちなみに以上、以下、大きい、小さいは次のようになる。以上とか以下って場合はその数字を含むということなので・・・

  1. if (a >= 1)
  2. if (a <= 1)
  3. if (a > 1)
  4. if (a <= 1)

となる。簡単だろ?

条件式な罠

実に罠の多い言語だな、Cって。実は条件式にもちょっとした罠がある。数値計算をやる人にとっては結構、致命的なので気をつけるべし。要はdouble型を使うときのことなのだ。え、floatは?だからそれは使うなと(ry

では、また罵倒するプログラムでも。

1	#include <stdio.h>
2
3	int main(void)
4	{
5		double val1 = 0.1, val2 = 0.02;
6
7		val1 *= 0.2;
8
9		if (val1 == val2)
10		{
11			printf("yes!\n");
12		}
13
14		return 0;
15	}

さてさて、またまた単純なプログラムだ。L5でdouble型でval1とval2を確保、で、それぞれを0.1と0.2で初期化。L7でval1は0.2倍する。前にも書いたとおりこれは"val1 = val1 * 0.2"と同じことだ。で、L9でval1とval2で比較、等しければL11で示すとおり、"yes!"と表示、で、終了、これだけだ。

当然だけどもし、あなたが小学4年以上であれば普通に考えると0.1*0.2は0.02なわけでこれを実行すれば"yes!"と表示されるはずであることは明らかだ。ところが、てっちゃんの環境だと・・・

% a.out
%

シーン・・・

何も表示されない。・・・あれ?

これがdoubleな罠なのだ。というか浮動小数点の罠だ。

ここまで、ちゃんと読んできてくれた心優しい方だとここで色々と「なるほど」って気になれる。これらのことだ。

そう、仮数部は二進法の固定小数を使っているのだ。ということは、あなたが"0.1"と書いた場合、コンピュータ内部はどうなっているか。"0.1"を二進数の固定小数にすると・・・

0.0001100110011001100110011......
=1.10011001100110011.... * 2100

と、なる。びっくり、実は"0.1"というのは二進法で表すとなんと、循環小数なのだ。

実は一般的に十進数では切りのいいと思う小数でも二進法にすると循環小数だったりするのだ。というか、そう思ってかからないと問題になる。ちなみに"0.2"も循環小数だ。これで「それ以前にコンピュータ内部では二進法をつかっているから・・・」の「・・・」の意味がわかっただろうか。十進数の見た目だけで循環小数かどうかの判断が出来ないのだ。

もう、あとは説明するまでもないな。循環小数の場合は有効数字が問題になってくるのは小学校で体験済みだ。"1 / 3"は"0.33333333......"、でどっかで打ち切ってそれを"3倍すれば"0.333333333 * 3 = 0.999999999"になるわけで、"1"にはならない、そういう感じだ。コンピュータだって無限桁は扱えない。

じゃあ、どうすればいいのか。やり方は色々あるけど、普通は例えばこんな感じにする。

1	#include <stdio.h>
2	#include <math.h>
3
4	int main(void)
5	{
6		double val1 = 0.1, val2 = 0.02;
7		double zero = 0.00001;
8
9		val1 *= 0.2;
10
11		if (abs(val1 - val2) < zero)
12		{
13			printf("yes!\n");
14		}
15
16		return 0;
17	}

L2に見慣れないのが入ったがこれは差しあたってL11にある"abs"ってのを使うおまじないと思えばいい。ちょうど、L1がL13にある"printf"を使うために書いたってのと同じだ。

で、今回はL7でzeroっていうdouble型の変数を作った。で、とりあえず、小さな値にしておく。

L11が本題だ。"abs(x)"ってのはxの絶対値を教えてくれるのだ。なのでこの場合、val1 - val2の結果の絶対値を教えてくれる。で、その値がzeroより小さければ成立とするのだ。

どういうことかというと結局、val1とval2の差の絶対値がzeroより小さければ一致したとするってことなんだ。もっと言うとL7で指定した小数の範囲が一致していればそれ以下の小数は何でもいいってことだ。裏を返せばdouble型の有効桁の一番最後あたりにつくであろう四捨五入のゴミは無視してくれるのだ。

要点は条件式の中でdouble型、float型を"=="を使って比較してはならない。不等号などを使って幅を持たせること。ちなみに今後、if文以外に条件式を他にも使う例が出てくるけどそれらにも当然、言えることだ。要注意。

と、いうわけで数値計算をやったことある人は常識だと思うが、これも知っているのと知らないのとでは嵌り具合が全然違うので紹介してみた。

くわしすぎるC