(1) (2) (3) (4) (5) (6) (7)

Perl 入門 (4)

繰り返し(1) while(A){ B } 続き

前回の課題の解答例を次に示します。学籍番号はM04-123とします。


 1: $sum = 0;
 2: $i = 1;
 3: while($i <= 4123) {
 4:     $sum = $sum + $i**2;
 5:     $i++;
 6: }
 7: print "1^2 + 2^2 + 3^2 + ... + 4123^2 = $sum\n";


上の例では特定の番号の場合の計算しかできません。 次のように while によるループを二重にすれば、複数の番号に対する計算を行うことができます (計算の効率は無視しています)。赤い文字で表示した部分は上のスクリプトとほぼ同じです。 番号のみを変数に取り替えていることに注意して下さい。


$j=4052;
while($j <= 4102){
  $sum = 0;
  $i = 1;
  while($i <= $j) {
    $sum = $sum + $i**2;
    $i++;
  }
  print "1^2 + 2^2 + 3^2 + ... + $j^2 = $sum\n";
  $j++;
}


出力結果は次のようになります:nm007a.txt。 自分の計算結果と比較して下さい。

自由課題:上の計算は同じことが何度も繰り返されていて非常に効率が悪くなっています。 もっと要領よく計算するようなスクリプトを書いてみましょう。

まず、次のスクリプト (nm008.pl) を見て、どのような動作をするか、考えてみましょう。 10行目のexp は指数関数です。 exp($n) で e$n を値として戻します。 したがって exp(1) の値は e1 = e となります。


 1: $n = 100000;
 2: $a = 1;
 3: $r = 1 + (1/$n);
 4: $i = 1;
 5: while($i <= $n) {
 6:     $a = $a * $r;
 7:     $i++;
 8: }
 9: print "$a\n";
10: print exp(1), "\n";


次に実際にファイルを作成して実行してみましょう。

I:\nyuumon2>jperl nm008.pl
2.71826823719229
2.71828182845905

I:\nyuumon2>

これは数列 an を (1 + 1/n)n で定めたときの、 a100000 の値を近似計算したものです。 まあまあ自然対数の底 e に近い値が得られています。 変数 $n に与える値をむやみやたら大きくすれば よい近似値が得られるというものでもないのですが、試しに $n の値を 1 から だんだん 4999 まで増やして、値の変化を見てみましょう(nm009.pl)。


 1: $n = 1;
 2: while($n < 5000){
 3:     $a = 1;
 4:     $r = 1 + (1/$n);
 5:     $i = 1;
 6:     while($i <= $n) {
 7:         $a = $a * $r;
 8:         $i++;
 9:     }
10:     print "$n : $a\n";
11:     $n++;
12: }



I:\nyuumon2>jperl nm009.pl
1 : 2
2 : 2.25
3 : 2.37037037037037
4 : 2.44140625
5 : 2.48832
6 : 2.52162637174211

途中省略

4991 : 2.71800956010872
4992 : 2.7180096146392
4993 : 2.71800966914909
4994 : 2.71800972363691
4995 : 2.71800977810101
4996 : 2.7180098325457
4997 : 2.71800988696617
4998 : 2.7180099413665
4999 : 2.71800999574467


I:\nyuumon2>
画面表示に時間がかかるため、終了まで時間がかかりますね。 なかなか e の値に近づいていきません。 このようなプログラムをいくら長時間実行させても、e の値がどんなものになるのか、 結論を出すことはできません。 今度は、次のプログラム(nm010.pl)を実行してみましょう。


 1: $S=0;
 2: $n=1;
 3: while($n < 10000){
 4:     $S = $S + (1/$n);
 5:     print "$n : $S\n";
 6:     $n++;
 7: }



I:\nyuumon2>jperl nm010.pl
1 : 1
2 : 1.5
3 : 1.83333333333333
4 : 2.08333333333333
5 : 2.28333333333333
6 : 2.45

途中省略

9990 : 9.78660558575914
9991 : 9.78670567584022
9992 : 9.78680575590427
9993 : 9.7869058259533
9994 : 9.78700588598933
9995 : 9.78710593601434
9996 : 9.78720597603034
9997 : 9.78730600603935
9998 : 9.78740602604335
9999 : 9.78750603604435


I:\nyuumon2>


これはSn = 1 + (1/2) + (1/3) + ・・・ + (1/n) と置いたときの、 S1 からS9999 を計算したものです。 もちろんこれは発散してしまいますが、この増え方を見ているだけでは なにかの値に収束するかどうかはよくわかりません。

一般に、無限級数の和を第n項までの和で近似する問題を考えると、 次の式のように誤差が生じます。この誤差をある小さい値よりも小さくするために、 n の値をどのくらいにとればよいのかというのは微積分の問題になります。

また近似値を計算する過程で、a1, …, an の おのおのの値を求めるときに生じる誤差や それらを合計するときに生じる誤差などが累積して更なる誤差が生じます。 ここで、 初めて見る人はちょっとびっくりする計算をしてみましょう。 次のスクリプト nm011.pl をダウンロードして実行してみてください。


 1: $sum = 0;
 2: $i = 1;
 3: while ( $i <= 1000 ) {
 4:     $sum= $sum + 0.01;
 5:     $i++;
 6: }
 7: print $sum, "\n";


変数 $sum に初期値 0 を代入しておいて、それに1000回 0.01 を加えるわけですから、 当然 0.01 x 1000 = 10 が出力されると期待されます。そうなりましたか? それでは次に、0.01 ではないもっと汚い(?)小数 0.01171875 を 10000個加える計算をしてみてください。どうなるでしょう。

結果

このような誤差がどのくらいになるのかを見積もることは非常に重要です。 これらに関しては2年次以降の講義で取り上げられるでしょう。 自分で勉強してみたい人に、よい参考書を紹介しておきます。 (印刷ミスは多いそうですが、ミスを見つけながら読むのはとても いい勉強になります。数学科の学生にぴったりの本です。)

シリーズ・数学の世界2『情報の数理』山本慎・著(朝倉書店)
(ISBN4-254-11562-8) 2800円


課題

無限級数  1 - (1/2) + (1/3) - ・・・ + (-1)n+1(1/n) + ・・・ の値を小数で表したとき、小数第5位の数字が何になるのか求めて下さい。 使用したスクリプト(nm013.plとしてください)も自分のページに掲載しなさい。 自分の答えが正しいと思う理由も説明して下さい。

この場合は Snの値は交互に増えたり減ったりしながら変わっていくので、 誤差の見積もりが容易です。

締切:12月3日 (金) 午後5時


繰り返し(1)' do { B } while(A)

この場合は、とりあえずブロックBが一度実行され、その後 A の値が調べられてループになります。

繰り返し(2) for(A; B; C){ D }

for( A ; B ; C ){ D }

  • D には、繰り返し実行する内容を記述します。
  • A には、繰り返しを開始する前のデータの初期化の方法を記述します。
  • B には、繰り返しを継続するための条件を示します。
  • C には、Dを実行した後、 継続条件のチェックを行う前に行うデータの変更方法を記述します。


例えば
for ( $i = 1 ; $i <= 10 ; $i++ ) {
    $sum = $sum + $i;
}
は、次と全く同じ働きをします:
$i = 1;
while ( $i <= 10  ) {
    $sum = $sum + $i;
    $i++;
}

繰り返し(2)' foreach $youso (@array){ D }

リストを指定して、そこからひとつずつ要素を取り出して一定の作業を行うことができます。

foreach $youso ( @array ){ D }

  • @array には、リスト・配列を指定します。
  • $youso には、取り出した要素の値を格納する変数を指定します。
  • D には、繰り返し実行する内容を記述します。


例えば次は上の二つの例と同じ働きをします:
foreach $i ( 1 .. 10 ) {
    $sum = $sum + $i;
}