whileで繰り返し

回数でない繰り返し

forは基本的に繰り返しの回数が分かっている時に使います。これに対して、何回やればよいかが分かっていないとき while を使います。

while 文

基本形です

初期の条件はwhileの前にやっておく
while (繰り返す条件){

        繰り返す内容(いくつかの文)
        (繰り返す内容で条件が変化するように条件を選ぶ)
}

具体例です。i= から sum が 1000 を越えるまで iを増やしながら繰り返します。

int sum = 0;
int i = 0;
while ( 1000 > sum ){

        i = i + 1;
        sum =sum + i;
}
printf ("%d まで足すと合計が 1000 を越え %d になります\n",i,sum );

注意

「繰り返す内容」に条件の変化がないと無限に繰り返すプログラムになってしまいます。

その時には [Ctrl]+[C] つまりコントロールキーを押しながらCを押します。

これはCancel(中止)をコンピュータに知らせるものです。

課題6-1 whileを使って繰り返し

プログラム名 k0601.c

 1: /* whileを使って繰り返し k0601.c */
 1: 
 2: #include <stdio.h>
 3: 
 4: int main()
 5: {
 6:     int max = 5000;
 7:     int sum = 0;
 8:     int i = 0;
 9:     while ( max > sum ){
10:         i = i + 1; 
11:         sum =sum + i;
12:     }
13:     printf ("%d まで足すと合計が %d を越え %d になります\n",i,max,sum );
14:     return 0
15: }

プログラム解説 k0601.c

9: while ( max > sum ){
ここが一番大切 sum が max より小さいうちは繰り返すということ。5000 を maxに代入して使うのはprintfでもまた使うから。
10: i=i+1
i の初期値を 0 にしておいて i=i+1 してここで初めて i の値を 1 にします。その理由は次で。
11: sum = sum + i
この行が終わった段階で i まで加えて sum になっています。この時の i が答えです。このまま while で sum の値をチェックします。
i の初期値を 1 にすると、sum = sum + i の後に i=i+1 することになっていまいます。この場合 while で sum の値をチェックして終わったときの i の値は正解より 1 多くなってしまいます。

実行結果はこうなります。

~/c$ ./k0601
100 まで足すと合計が 5000 を越え 5050 になります

課題6-2 ほんとに何回やればよいかわからない

10進数の0.1を二進数に書きなおすと循環小数になります。ちょうど1/3が0.33333...となるようなものです。コンピュータはこれを有限の桁で表現しますから誤差が出ます。0.1を何回も足し算せばこの誤差が大きく目立ってくるはずです。これを調べてみます。

プログラム名 k0602.c

 1: /* 誤差を確かめる k0602.c */
 2: #include <stdio.h>
 3: 
 4: int main()
 5: {
 6:     float x = 0.1;
 7:     float sum = 0;
 8:     int i = 0;
 9:     while ( (x > x*i-sum) && (x > sum-x*i) ){
10:         i=i+1;
11:         sum =sum + x;
12:         printf ("%d %f %f\n",i,x*i,sum );
13:     }
14:     printf ("%f を %d 回足すと合計が\n",x,i);
15:     printf ("%f のはずが\n",x*i);
16:     printf ("%f になります\n",sum );
17:     printf ("%f だけ違います\n",x*i-sum );
18:     return 0;
19: }

プログラム解説 k0602.c

6: float x = 0.1;
0.1は循環小数になるのでこれを足し続けます。
7: float sum = 0;
x を sum に加え続けます。
8: int i = 0;
加えた回数を記録します。x に i を掛けるとより正確な値(誤差はi倍になるけどsumほどでない)が出ますから、これと sum を比較します。
9:
x*isumの差が大きくなってxの程度になったところで計算を終わらせたいのですが、どちらが大きいか分かりませんので、順序を変えて引き、どちらかがxより大きくなったら終わりにします。 && は「かつ」で前後の比較式が両方共「真」になったときに「真」になります。真である限り続けるのがwhileですから、どちらかがxより大きくなったら終わりになります。
&& のようなものを論理演算子といいます。
&&> の間にも +* の間にあったような優先順位があり、この例では ( ) は不要ですが、見間違えないように書くことをおすすめします。
10:-12:
whileの繰り返しの中では i で繰り返しの回数を数え、sum に x を加え、x*i と sum の値を並べて表示します。
14:-17:
x より大きな差がでたところで結果を改めて報告します。

実行結果は次のとおり。

~/c$ ./k0602
....(略)....
10114 1011.400024 1011.300110
10115 1011.500000 1011.400085
10116 1011.600037 1011.500061
10117 1011.700012 1011.600037
10118 1011.799988 1011.700012
10119 1011.900024 1011.799988
0.100000 を 10119 回足すと合計が
1011.900024 のはずが
1011.799988 になります
0.100037 だけ違います

別のコンピュータでは答えがちょっと違います。右の0.1を加えていく計算は同じ様ですが、掛け算で誤差の累積しないより正確な値を出そうとしているところが異なります。ここはfloatとintと異なる型の変数の計算で、しかもintは32ビットOSと64ビットOSでは使用バイト数が異なることがありますからそれが影響しているのかもしれません。

~/c$ ./k0602
....(略)....
10114 1011.400015 1011.300110
10115 1011.500015 1011.400085
10116 1011.600015 1011.500061
10117 1011.700015 1011.600037
10118 1011.800015 1011.700012
0.100000 を 10118 回足すと合計が
1011.800015 のはずが
1011.700012 になります
0.100003 だけ違います

floatは4バイトで2進数で精度(計算の細かさ)は23+1で24ビットです。十進表現での精度に直すと6~7桁の精度ですから、1011.800から下の値は信用できないのです。詳しいことは小数の表現(詳)を参照してください。

課題6-3 doubleで

k0602.cに比べて色のついた部分が異なっています。

プログラム名 k0603.c

 1: /* doubleの誤差を確かめる k0603.c */
 2: #include <stdio.h>
 3: 
 4: int main()
 5: {
 6:     int i = 0;
 7:     double sum = 0;
 8:     double x = 0.1;
 9:     while ( (x > x*i-sum) && (x > sum-x*i) ){
10:         i=i+1;
11:         sum =sum + x;
12:         printf ("%d %f %f\n",i,x*i,sum );
13:         if (i>=600000){break;}
14:     }
15:     printf ("%f を %d 回足すと合計が\n",x,i);
16:     printf ("%f のはずが\n",x*i);
17:     printf ("%f になります\n",sum );
18:     printf ("%f だけ違います\n",x*i-sum );
19:     return 0; 
20: }

プログラム解説 k0603.c

7: 8: double
floatdouble に換えました。これで精度が上がります。二進表現での精度(計算の細かさ)は52+1で53ビットです。十進表現での精度に直すとほぼ15桁の精度です。
13: break;
この計算はかなり待っても終わりません。そこで i が 6000000 以上になったら while の条件に関わりなく while を終了する指示 break; をしています。(ifのブロックに1行しか文がないときは{ }を省略できますが、見間違えないようにあえて書くことをおすすめします)

実行結果はこうなります。

~/c$ ./k0603
....(略)....
599989 59998.900000 59998.899999
599990 59999.000000 59998.999999
599991 59999.100000 59999.099999
599992 59999.200000 59999.199999
599993 59999.300000 59999.299999
599994 59999.400000 59999.399999
599995 59999.500000 59999.499999
599996 59999.600000 59999.599999
599997 59999.700000 59999.699999
599998 59999.800000 59999.799999
599999 59999.900000 59999.899999
600000 60000.000000 59999.999999
0.100000 を 600000 回足すと合計が
60000.000000 のはずが
59999.999999 になります
0.000001 だけ違います

論理演算子

いろいろな条件を組み合わせて使うための演算子です。

論理演算子 意味 説明 真になる例 偽になる例
! not (否定) 後ろにある値を逆にする(真ならば偽) !0 !(1==1)
&& and (かつ) 前後にある値が共に真のときだけ真 真 && 真 真 && 偽, 偽 && 偽
|| or (または) 前後にある値の少なくとも1つが真ならば真
(前後にある値が共に偽のときだけ偽)
真 || 真, 真 || 偽 偽 || 偽

演算子の優先順位

不要でも( )をつけた方が見やすい場合もあります。自信が持てないときはためらわず( )をつけましょう。

演算子(上が優先) 演算子の種類
! ++ --否定演算子、インクリメント,ディクリメント
* / %算術演算子
+ -算術演算子
< <= > >= 比較演算子
== != 比較演算子
&&AND演算子
||OR演算子
= += -= *= /= %=代入演算子
聖愛中学高等学校
http://www.seiai.ed.jp/
Dec. 2011