プログラミング演習1


― 課題3 数値解析法 ―

2. Cによる数値解析のための準備2

2.1 レポート作成の技術1
2.1.1 Excelによるグラフ描画

出力データをCSV形式で保存すれば、 Excelでただちに読み出すことができる。 CSV形式は一組のデータを単にコンマで区切り、組ごとに改行すればよい。 ファイル保存で名前をつける際、拡張子を“.csv” とする。 先頭行にデータの名前を入れておくと後で便利である。

以下の例は後述する二分法の出力ファイルで、 名前を“sample.csv”とする。


 n, x, err
   1, 1.500000000000000e+000, 5.000000000000000e-001
   2, 1.250000000000000e+000, 2.500000000000000e-001
   3, 1.375000000000000e+000, 1.250000000000000e-001
   4, 1.437500000000000e+000, 6.250000000000000e-002
   5, 1.406250000000000e+000, 3.125000000000000e-002
   6, 1.421875000000000e+000, 1.562500000000000e-002
   7, 1.414062500000000e+000, 7.812500000000000e-003
   8, 1.417968750000000e+000, 3.906250000000000e-003
   9, 1.416015625000000e+000, 1.953125000000000e-003
  10, 1.415039062500000e+000, 9.765625000000000e-004
  11, 1.414550781250000e+000, 4.882812500000000e-004
  12, 1.414306640625000e+000, 2.441406250000000e-004
  13, 1.414184570312500e+000, 1.220703125000000e-004
  14, 1.414245605468750e+000, 6.103515625000000e-005
  15, 1.414215087890620e+000, 3.051757812500000e-005
  16, 1.414199829101560e+000, 1.525878906250000e-005
  17, 1.414207458496090e+000, 7.629394531250000e-006
  18, 1.414211273193350e+000, 3.814697265625000e-006
  19, 1.414213180541990e+000, 1.907348632812500e-006
  20, 1.414214134216300e+000, 9.536743164062500e-007

 sqrt(2) = 1.414213657379150e+000

  1. “sample.csv”のアイコンをクリックし、ファイルを保存する。
    • 保存したファイル(sample.csv) には Excel が関連付けられているので、 Excel が起動し、sample.csv が読み込まれる。(起動しない場合は、Excel を立ち上げてからsample.csvを読み込むこと:ファイル(F)から開く(O)とする)
  2. グラフを作成するために、データの範囲を選択する。 以下の操作により、データ範囲をすばやく選択できる。
    • セル A1 が選択されていることを確認。
    • [Ctrl] キーと [Shift] キーを同時に押しながら [] キーを押す。
    • [Ctrl] キーと [Shift] キーを同時に押しながら [] キーを押す。
    • 数値の範囲が反転し、選択されていることを確認する。
  3. [挿入]→[グラフ]→[散布図(直線とマーカー)]を選択。
  4. グラフ右上の [+] からグラフ要素の[軸ラベル]を選択し、 「横軸ラベル」を、“n” に書き換える。 また「縦軸ラベル」を、“x, err” に書き換える。
  5. [縦軸] ダブルクリックし、「軸の書式設定」の「軸のオプション」の [表示形式] で表示を標準にする。
  6. 変更したい枠線はダブルクリックして、[枠線の色] などで変更する。当然、[線なし] も可能である。
  7. 好みに応じて軸の目盛の範囲や凡例の位置、背景などを変更する。
  8. グラフを(右クリックから)コピーして Word など他のアプリケーションに貼り付けることが可能である。

bisection1

練習1 (Excelによるグラフの描画)
(1)まず“sample.csv”を右クリックし、
「名前を付けてリンク先を保存(A)...」で演習1課題3用のフォルダ(例えば“Z:\proen1\kadai3”)に保存する。
(2)実際に上記の操作を行い、グラフを描いてみよ。
練習2:発展 (Excelによる3Dグラフの描画)
上記と同様に、“sample2.csv”を右クリックで保存し、3Dグラフ(等高線)を描くこともできる。
(1)下図のようなグラフを描いてみよ。

3d-image


2.1.2 PADについて

PADとは Problem Analysis Diagram の略である。 構造化フローチャートの一種で、処理の方向は「上から下へ」流れる。 具体的な処理内容が書かれた「処理箱」と呼ばれる横長の四角い箱の左端を「縦線で結合(連接)」することによりプログラムのフローをあらわす。Cにおいては、処理箱は文に、連結されたものはブロックに相当する。

seq

「判断(選択)」、「反復」などの処理の中の文・ブロックはそれらの箱の右側に置き、横線で結ぶ。これらの処理ではフローをいったん右側の文・ブロックに移す。ブロックの場合は上からはじまり、一番下で左側に戻る。

「判断(if文)」は、箱の右辺が中折れしており、上下の頂点から横に1本ずつ2本の分岐線を伸ばすことができ、条件によっていずれか1つの分岐のみが実行される。

if

「前判定反復(while文)」は、箱の左側を二重線とする。

while

「後判定反復(do~while文)」、「問題向き反復(for文)」は箱の右側を二重線とする。 Cでの後判定反復の実装には do~while文 を用いる。
★【注意:PADの表記】「do~while」後は継続条件であるため、“表記はそのまま”でOK。
しかし、参考資料によっては「repeat~until」を主とする(終了条件となる)ため、“表記反転”となる場合がある。

repeat
for

「定義」、「引用(参照)」は箱の内部に手続き名などを書き、両側あるいは境界を二重線とする。定義は、箱の右側から二重線を横に出して処理ブロックに結ぶ。

def

引用(参照)は箱の両側または境界が二重線であること以外は処理箱と同様の扱いとなる。

ref

PADのテンプレート [更新:2018/6/19]

  1. ここ(pad-new.ppt)を右クリックし、「対象をファイルに保存」で演習1_課題3用のフォルダに保存する。
  2. pad-new.pptを開き、ファイル内のPADをPowerPointの新規スライドにコピー・貼り付けし、グループ解除し、加工する。
  3. 加工し終わったら作図ソフト(ペイントツール等)を立ち上げ、新規ページを小さめにしておく。
  4. 完成したPADをPowerPoint上でドラッグ選択し、コピーする。
  5. 作図ソフト(ペイントツール等)上で新規ページに貼り付け、画像ファイルとして名前をつけて保存する。
Wordなどで挿入できるファイル形式(jpeg, gif, tiffなど)にしておけば、レポート作成に利用できる。

また、Word上で書いたPADのサンプルも用意した。次のように、判断(選択)の箱をフローチャートの「記憶データ」で代用している。 レポートにはこちらの方法で描いてもよいことにする。

pad_doc

補足説明:フローチャートとPADの比較(PADの利点)

これまで使用してきたフローチャートは、プログラムの処理の流れを
説明する最も一般的な表現方法である。ただし、下記2つの欠点がある。

(1)同じ処理手順のフローチャートを書く場合でも、人によって様々な
ものができてしまうこと。
(2)プログラムの流れを書くものであるという性質上、実際にプログラム
を書くときに翻訳の作業を必要とすることである。

これに対し、PADは上記でも説明した通り、「上から下へ」、「左から右へ」
というように、表現方法に規則がある。つまり、書き方に自由度があまりなく、
誰が書いても決まったものとなる。更に、プログラムに翻訳するときに、
フローチャートほど悩まずに書くことができる。

◎その意味では、「PADを書くこと」≒「プログラミングすること」が
PADの利点となる。

以下に示すような処理をフローチャートとPADで書いた場合を比較する。

====(抜粋)============
if (条件1){動作A   ・・・・・・・“YES”のとき
}          ・・・・・・・“NO” のとき 
else if (条件2){動作C  ・・・・・“YES”のとき
}              ・・・・・“NO” のとき 
     else if (条件3){動作D ・・・“YES”のとき
}        何もしない ・・・“NO” のとき 
        end if 
    end if
end if
動作B
====(抜粋)============
flowchart
pad-flow
2.2 レポート作成の技術2(テクニック)
2.2.1 関数引数

非線形方程式などのサブルーチンを作成する場合、対象となる関数や方程式が替わるたびにサブルーチン内の関数名を書き換える必要がある。

しかし、サブルーチンの引数の一つに関数名を指定できるようにすれば、サブルーチンを呼び出すメイン側に関数名の決定権を譲ることになり、非常に汎用性の高いサブルーチンとなる。

それを実現するために使われるのが関数引数である(熊谷・玉城・白川「例題で学ぶC言語」の第11章 11.5 関数引数 pp.106-108 を参照のこと)。

例えば、後述する「二分法」のサブルーチン bisection で方程式 f (x)=0 の左辺の関数 f (x) を任意の名前で呼び出せるようにするには、 仮の関数名 f の前に*(アスタリスク)を付けてカッコで閉じた 関数名 (*f)(これを関数ポインタという)を用い、 これをサブルーチンの引数リスト内に関数の引数(型のみでよい)をつけて double (*f)(double) のように定義しておく。

double bisection(double (*f)(double), double x1, double x2)
{
    二分法の本体
}
さらに、このサブルーチン内部で関数を呼び出すときは
    if (f(x1) * f(x3) < 0) x2 = x3;
というように引数リスト内で定義した仮の関数名(この場合は f( ) )で呼び出すようにしておく。

方程式の左辺の関数が実際には func1(x) という名前で定義されているなら、 main関数で二分法のサブルーチンを呼び出すときに

    x = bisection(func1, x1, x2);
実際の関数名引数なしで指定して呼び出せばよい。

練習3 funcp.c(関数引数の使用)
 1: #include <stdio.h>
 2:
 3: double func1(double x) // f(x) = x を返す関数
 4: {
 5:     printf("# func1(% g) is called", x);
 6:     return x;
 7: }
 8:
 9: double func2(double x) // f(x) = x^2 を返す関数
10: {
11:     printf("# func2(% g) is called", x);
12:     return x*x;
13: }
14:
15: double func_p(double (*f)(double), double x) // 関数引数を用いたサブルーチン
16: {
17:     double y;
18:
19:     y = f(x); // ここでは仮の関数名 f() を用いる
20:     printf(" in func_p().\n");
21:     return y;
22: }
23:
24: int main(void)
25: {
26:     printf("func1(1) = % g\n", func_p(func1,1)); // func1 を指定して func_p を呼び出す
27:     printf("func2(2) = % g\n", func_p(func2,2)); // func2 を指定して func_p を呼び出す
28:     return 0;
29: }

◎上記プログラムを実行してみよ。関数引数の使い方を確認せよ。
[実行結果]
-----------------------
# func1( 1) is called in func_p().
func1(1) = 1
# func2( 2) is called in func_p().
func2(2) = 4
-----------------------


2.2.2 コマンド行の引数
(→ カーニハン、リッチー 「プログラミング言語C」 第5章 5.10 (pp.139~140) 参照)

Windowsのコマンドプロンプト(DOSプロンプト)などではプログラム実行時に直接パラメータを引き渡す方法が用意されている。例えば、Cで作られた大多数のDOSコマンドなどは、実行時のパラメータにより機能を切り替えられるようになっている。

プログラムに汎用性を持たせる段階になると、初心者は入力促進させてパラメータや数値を入力させる「対話的なプログラム」を作りがちだが、実はこれは人間が自らコンピュータにこき使われる状況を作り出しているといえる。 最初にパラメータを用いて内容を指示したあと、延々と実行させてほっておき、仕事が終わったら知らせるといった「バッチ処理」こそ、コンピュータにこき使われずに人間が楽できる本来の使い方である。そのうまみを享受するためには、汎用性のあるプログラムにコマンド行の引数を渡す方法を早めにマスターするのがよい。

main関数の引数として以下のように書くと、コマンド行の引数が利用できるようになる。
int main(int argc, char *argv[])
整数変数 argc (argument countの意味) にはコマンド行の引数の個数が入る。 文字列配列ポインタ argv (argument vectorの意味) にはコマンド行の引数の内容が格納される。 最初の文字列 argv[0] には起動したプログラムのコマンド名が入り、 これも argc の数に含まれる。 2つめ以降の argv[1], argv[2],... にはコマンドに続く引数が、 スペースによって区切られて、文字列として順に格納される(mainarg.c)。
 1: #include <stdio.h>
 2: 
 3: int main(int argc, char *argv[])
 4: {
 5:     int i;
 6:
 7:     for (i = 1; i < argc; i++) printf("%s ", argv[i]);
 8:     printf("\n");
 9: 
10:     return 0;
11: }
◎上記プログラムを実行してみよ。
[実行結果]
-----------------------
Z:\>mainarg 1.0 234 abc !"#
1.0 234 abc !#
-----------------------

ただし、数値を引数にしたつもりでも文字列として引き渡されるので、 数値を変数に代入する際には数値変換関数(atoi(), atol(), atof())を用いて 変換する必要がある。 例えば実数を3つ入力したい場合は、以下のようにすればよい (mainarg3.c)。

 1: #include <stdio.h>  // printf
 2: #include <stdlib.h> // atof
 3: 
 4: int main(int argc, char *argv[])
 5: {
 6:     double x1, x2, eps;
 7: 
 8:     if (argc < 4) {
 9:         printf("usage: %s x1 x2 eps\n", argv[0]);
10:         return 1;
11:     }
12:     x1 =atof(argv[1]);
13:     x2 =atof(argv[2]);
14:     eps=atof(argv[3]);
15: 
16:     printf("[% g, % g] % e\n", x1, x2, eps);
17:
18:     return 0;
19: }
◎上記プログラムを実行してみよ。
[実行結果] ※argc の数にはコマンド名も含まれるため、引数の個数よりも1つ多いことに注意。
-----------------------
Z:\>mainarg3 1 2.0 1e-6
[ 1, 2] 1.000000e-006
-----------------------


2.2.3 破局のbreak文、未練のcontinue文(エラーや規定数を超えた場合に使用)

break文の使い方の一つに ループ(反復)からの脱出がある。break文に出会うと、プログラムは その文を含む最小のループ(for, while, do~while)から抜け出す。 if文と組み合わせることで、ループ内の任意の場所から条件付きで 抜け出すことができる。

    do {
        n++;
        …(1)…
        if (n > N) {
            printf("*** 反復回数が%d回を超えました。 ***\n", N);
            break;
        }
        …(2)…
    } while (err >= eps);

break文とよく似たものにcontinue文がある。 continue文はループの中でのみ用いられ、この文以降の文を省略してループの最後までジャンプする。 ループの外には出ないことがbreak文との違いである。

continue文が出てくるシチュエーションでは、if文を使えば代用できる。 また、break文が出てくるシチュエーションでは、continue文とループでの条件文の追加で代用できないことはないが、同種の条件式を2か所で使うことになる。

    do {
        n++;
        …(1)…
        if (n > N) {
            printf("*** 反復回数が%d回を超えました。 ***\n", N);
            continue;
        } else {
            …(2)…
        }
    } while ((err >= eps) && (n <= N));

他にも、多重ループを一気に抜けるにはgoto文が便利である。 また、関数(メイン関数の場合にはプログラム)を強制終了させるには return文、 サブルーチン内からプログラムを強制終了させるにはexit関数を用いる。


2.2.4 デバッグに便利な定数マクロ

Cでは、標準で __FILE__, __LINE__ などの定数マクロが用意されている。 これは、printf と共に使えば有用なデバッグツールになる。 __LINE__ にはソース内の行番号が入る。 __FILE__ にはソースのファイル名が入る。 文字列定数なので、printf 内では他の文字列と連結できる。 ソースのはじめに #define でマクロ定義しておくと便利である。

1: #include <stdio.h>
2: #define CLINE printf(__FILE__":%d\n", __LINE__)
3:
4: int main(void)
5: {
6:     printf("hello, world!\n"); CLINE;
7:     return 0;
8: }

2行目は、

2: #define CLINE printf("%s:%d\n", __FILE__, __LINE__)

としてもよい。 たとえば、上記のプログラム hello2.c の実行結果は

hello, world!
hello2.c:6

となる。途中でフリーズするようなソースをデバッグする際に CLINE; をブレークポイント代わりにあちこち入れておくと、 どこまで実行しているかがデバッガを使わなくても追跡できる。


【目次】 | 【1.】 | 【3.】