2007 年度「計算機基礎論3B」 2007-11-09

§20 ここまでの知識で書けるいくつかのプログラム

(20.1) アラレ(霰)数とは、

という漸化式で定義される数列である。 この数列は、初項がいかなる自然数であってもそのうち 1, 4, 2, 1, 4, 2, ... というループに落ち着くであろうと予想されているそうだ。 (「霰」とは、空から降るあの「あられ」である。)

(20.2) 次のプログラムは、ユーザが入力した数から始めて 1 になるまで、 この数列を出力する。 for の小カッコの中は二つのセミコロンで三つに区切られるが、 何もない部分は空白にしておくことができる。 下の例では継続条件しかない。 (こういった場合、while を使うほうがはるかに自然である。)

#include <stdio.h>  /* アラレ数 */

main() {
    int n;

    printf("自然数を入れてください.\n");
    scanf("%d", &n);
    for (  ; n > 1;  ) {
        if (n % 2 == 0) {       /* n が偶数のとき */
            n = n / 2;
        } else {                /* n が奇数のとき */
            n = 3 * n + 1;
        }
        printf("%d ", n);
    }
    printf("\n");
}

(20.3) この例では中カッコが入れ子になっている。 その場合のインデントの(一つの)やり方は以下の通りである。 上のプログラムで確かめよ。

  1. {」を打ったらその直後で改行し、 次の行はその行の(空白以外の)頭よりもタブ一つ分(ここでは空白四文字分) 下げて始める。 (ただし、コメントは「{」の右に書いても構わない。)
  2. }」を打つときは、 それに対応する「{」を含む行の(空白以外の)頭の文字とそろえる。
  3. その他の行の頭は前の行とそろえる。
これらの規則はすぐに慣れることができるし、 これらを守ってさえいれば、「ある中カッコとペアになる中カッコをさがす」 ことや「ある文を囲む最小の中カッコをさがす」ことはきわめて容易である。 前にも言ったが、 自分流のインデントをすでに編み出している人はそれで構わない。 そうでない人はまずはこれを覚えよ。 また、プログラムを書き上げてから最後にインデントを整えるのではなく、 きちんとインデントしながら書く習慣をつけるとよい。 そのほうが、いままで書いた部分をよりよく理解できるので、 より整理された頭でプログラムを書くことができる。

(20.4) 自然対数の底 e を e = 1/0! + 1/1! + 1/2! + 1/3! + ... として計算するプログラムである。 階乗は、C言語には用意されていないので、自力で計算しなければならない。



#include <stdio.h>  /* 自然対数の底 e の計算 */

main() {
    int n;
    double sum, factorial;

    sum = 1;
    factorial = 1;

    for (n = 1; n < 23; n++) {
        factorial = factorial * n;
        sum = sum + 1 / factorial;
        printf("第 %2d 項までの和は %.53f です.\n", n, sum);
    }
}

(20.5) int 型と double 型をいっしょに使うときはこのように宣言する。 順序は逆でも構わない。 %2d は「二ケタの幅で表示せよ」の意味、 %.53f は「小数点以下 53 ケタで表示せよ」の意味である。 このプログラムでは double 型と int 型との間で四則演算を行なっている。 その場合、結果は double 型になる。 くわしくは K&R2 §2.7, A6 を参照のこと。

(20.6) ※ センターの linux 上のCコンパイラでは、 第 3 項までの和 1+1+1/2+1/6 = 2+2/3 の小数点以下十数ケタ目ですでに違っている。 よって、 上のプログラムの計算結果は小数点以下十数ケタ目あたりまでしか信頼できない。 コンピュータの計算は近似計算なので、 むやみに桁数を多く表示させても意味がないときがある。 53 ケタ目まで表示させたのはそのことを知ってもらうためにすぎない。 (岩波「数学辞典第3版」1434 ページによれば e = 2.7182818284590452353... である。)

(20.7) ※ 計算は第 22 項までで打ち切っているが、これは試行錯誤をくり返しながら決めたもの。 「収束したら打ち切る」とすることもできなくはないが。

(20.8) 「かつ」や「または」は次のように書く。 これらは for の継続条件にも使える。 K&R2 では §2.6 を見よ。

    if ((x > 0) && (x < 8)) {       /* 「かつ」 */
        ........
    }

    if ((x < 0) || (x > 2)) {       /* 「または」 */
        ........
    }
x > 0 などを囲んでいる小カッコは実は不要である。 これについての詳細は K&R2 §2.12 を参照。 「0 < x < 8」はC言語でも意味のある式だが、 数学とは意味が異なるので上の意味では使えない。

(20.9) 「かつ」も「または」も同じ文字を二つ打つことに注意。 まちがって「&」「|」をひとつだけ書いた場合、 それらにも意味があるのでコンパイラはエラーメッセージを出さない (かもしれない)。

§21 二重ループ

(21.1) 次のプログラムは九九の表を出力する。 for を二重に使っていること以外は、 いままでに習ったことでわかると思う。

(21.2) 二重ループについて説明する。 まず外側のループから始まる。 最初は i が 1 で、 外側のループの中カッコにはいる。 するとそこに内側のループがあり、 j が 1 から 9 までこの内側のループを回る。 それが済んで改行を出力すると外側のループの中カッコの中がおわる。 そこで外側のループの再初期化にゆき、 i が 1 だけ増えて 2 になる。 これを i が 9 になるまでくり返す。

#include <stdio.h>

main() {
    int i, j;

    for (i = 1; i <= 9; i++) {
        for (j = 1; j <= 9; j++) {
            printf(" %d", i*j);
        }
        printf("\n");
    }
}

(21.3) 次は、素因数分解のプログラム。 三重ループになっている。 素因数が見つかるまで 2, 3, 4, ... と順に割ってみる素朴なやり方なので、 素因数分解される数が一億ぐらいになるとちょっと時間がかかる場合がある。 (これは、ここまでの知識だけを使って“強引に”書いてみたプログラムなので、 あまりうまくないと思う。 読んで理解できなくても心配する必要はないし、 まねするのがいいかどうかもわからない。)

#include <stdio.h>

#define FROM    1   /* この数から */
#define TO    100   /* この数まで */

main() {
    int n, m, p, i;

    for (n = FROM; n <= TO; n++) {      /* n を素因数分解する */
        printf("%d =", n);
        for (m = n; m != 1; m = m/p) {  /* m を割る最小の素数を p に入れる */
            p = 0;                      /* 0 は「まだはいっていない」の印 */
            for (i = 2; p == 0; i++) {  /* 2, 3, 4, ... と順に割ってゆく */
                if (m % i == 0) {       /* 割り切れたら */
                    p = i;              /* p に格納 */
                }
            }
            printf(" %d", p);
        }
        printf("\n");
    }
}

§22 練習問題

全部やらなくても構いません。 むずかしさはまちまちです。

  1. 1 から始めて 1, 2, 4, 8, 16, ... と順に倍になってゆく数列を表示するプログラムを書け。 2 の何乗で答えがおかしくなるだろうか?
  2. (18.2) のプログラムを改変し、 「いくつまで足しますか?」に 10 を入力すると
    1 から 1 まで足すと 1 です.
    1 から 2 まで足すと 3 です.
        ....
    1 から 10 まで足すと 55 です.
    
    と出力されるようにせよ。
  3. 鶴亀算は知らないものとし、 全ての可能性を調べることで答えを見つけるプログラムを書け。 次が出力例。ただし「5」と「16」はユーザが入力したものである。
    ツルとカメを合わせて何匹いますか?
    5
    足は何本ですか?
    16
    ツル 0 匹、カメ 5 匹のとき足は 20 本で、これは答えではありません。
    ツル 1 匹、カメ 4 匹のとき足は 18 本で、これは答えではありません。
    ツル 2 匹、カメ 3 匹のとき足は 16 本で、これが答えです。
    ツル 3 匹、カメ 2 匹のとき足は 14 本で、これは答えではありません。
    ツル 4 匹、カメ 1 匹のとき足は 12 本で、これは答えではありません。
    ツル 5 匹、カメ 0 匹のとき足は 10 本で、これは答えではありません。
    
  4. (21.1) の九九のプログラムを、桁をそろえてきれいに表示するように改良せよ。次に、
       |  1  2  ...  9
    ---+--------...----
     1 |
     2 |
     : :
     9 |
    
    のように被乗数・乗数を左と上に出力するようにしてみよ。
  5. 2 以上の自然数 n を一つ固定する。 “数”としては 0, 1, 2, ..., n-1 だけを考え、 足し算、掛け算は結果が n 以上になったら n で割った余りをもって答えとする。 この掛け算の表を出力するプログラムを書け。 n はプログラム中では「#define N 12」のようにし、 容易に変更できるようにせよ。 そして、いろいろな n に対しての出力結果を見ながら、 「一番上の行・一番左の列を除く各行・各列に 0, 1, 2, ..., n-1 がすべて現れるのは n がどのような数の場合か?」という数学の問題を考えてみよ。 この“数”を数学では Z/nZ, Z/n, Zn などと書き表す。
  6. 0 は偶数です.
    1 が奇数です.
    2 は偶数です.
      ...
    9 は奇数です.
    10 は偶数です.
    
    のように出力されるプログラムを書け。
  7. ユーザが入力した数から始めて、 次々と平方根を計算して出力するプログラムを書け。
  8. ユーザが入力した数の約数を全て出力するプログラムを書け。
  9. ユーザが入力した二つの自然数の最大公約数を出力するプログラムを書け。
  10. ユーザが入力した二つの自然数の最小公倍数を出力するプログラムを書け。
  11. プログラムの実行にかかった時間を調べるには 「date; ./a.out; date」 として開始前後の時刻を出力させるのが最も簡単であろう。 一億回まわるループ
        for (i = 0; i < 10000; i++) {
            for (j = 0; j < 10000; j++) {
                sum = sum + (i + j);    /* 意味のない適当な計算 */
            }
        }
    
    はどのくらい時間がかかるか? 回数を変えていろいろ調べてみよ。
  12. (20.4) のような、無限級数の和(の近似値)を求めるプログラムを書け。 等比級数でもよい。
  13. 1/1 + 1/2 + 1/3 + ... + 1/n + ... は無限大に発散するが、 これを実際に計算してみるプログラムを書いてみよ。 また、第 n 項までの和と log(n) との差はオイラー定数と呼ばれる数に収束することが知られている。 それも計算してみよ。 (nint 型変数として 1/n と書くと整数どうしの割り算なので n が 2 以上のときは 0 になってしまう。 1.0/n と書けば結果は double 型になり、 小数点以下まで計算される。)


岩瀬順一