Prog Lec14-1 Copyright (C) 1999 – 2013 by Programming-0 Group プログラミング入門 第1 4 回講義 マークのあるサンプルプログラムは /home/course/prog0/public_html/2013/lec/source/ 下に置いてありますから、各自自分のディレクトリに コピーして、コンパイル・実行してみてください マークのあるサンプルプログラムは /home/course/prog0/public_html/2013/lec/source/ 下に置いてありますから、各自自分のディレクトリに コピーして、コンパイル・実行してみてください 関数(その2) 関数の復習 (2) 関数の例1 ニュートン法 (4) 関数の例2 桁数 (11) 関数の例3 ある桁の数 (14) エラーチェックと強制終了 (17) 関数の例4 2進 → 10進変換 (20)
Prog Lec14-2 Copyright (C) 1999 – 2013 by Programming-0 Group #include double nijou(double); main() { double a = 1.73, b, c ; b = nijou(a); c = nijou(1.41);... (以下略) } double nijou( double x ) { double y; y = x * x; return y ; } #include double nijou(double); main() { double a = 1.73, b, c ; b = nijou(a); c = nijou(1.41);... (以下略) } double nijou( double x ) { double y; y = x * x; return y ; } 引数が仮引数にコピーされる 関数内で仮引数の値に従って 計算(処理) 戻り値が関数の値となる main の前にプロトタイプ宣言 main の後ろに関数本体の宣言 関数の復習 1
Prog Lec14-3 Copyright (C) 1999 – 2013 by Programming-0 Group #include double nijou(double); main() { double x = 2.0, a1, a2, a3; a1 = nijou(3.0); /* 値 */ a2 = nijou(x); /* 変数 */ a3 = nijou(x * 2.0); /* 式 */ printf("a1:%f a2:%f a3:%f \ n", a1, a2, a3); } double nijou(double x) { double y; y = x * x; return y ; } #include double nijou(double); main() { double x = 2.0, a1, a2, a3; a1 = nijou(3.0); /* 値 */ a2 = nijou(x); /* 変数 */ a3 = nijou(x * 2.0); /* 式 */ printf("a1:%f a2:%f a3:%f \ n", a1, a2, a3); } double nijou(double x) { double y; y = x * x; return y ; } 1.main の最初の実行文から開始 2.nijou(3.0) が評価される が関数の仮引数 x にコピーされ、関数 nijou に実行が移る 4. 計算の結果、 y に 9.0 が入り、「 return y 」 で 9.0 が nijou 関数の結果となる 5.nijou 関数の結果が a1 に代入される。 6. 次に nijou(x) が評価される 7.x(2.0) が関数の x にコピーされ、関数 nijou に実行が移る 8. 計算の結果 y に 4.0 が入り、「 return y 」で 4.0 が nijou 関数の結果となる 9.nijou 関数の結果が a2 に代入される 10.a3 も同様に計算される 11.printf で a1,a2,a3 が表示される 12. 終了 (教科書 P165 参照) 1.main の最初の実行文から開始 2.nijou(3.0) が評価される が関数の仮引数 x にコピーされ、関数 nijou に実行が移る 4. 計算の結果、 y に 9.0 が入り、「 return y 」 で 9.0 が nijou 関数の結果となる 5.nijou 関数の結果が a1 に代入される。 6. 次に nijou(x) が評価される 7.x(2.0) が関数の x にコピーされ、関数 nijou に実行が移る 8. 計算の結果 y に 4.0 が入り、「 return y 」で 4.0 が nijou 関数の結果となる 9.nijou 関数の結果が a2 に代入される 10.a3 も同様に計算される 11.printf で a1,a2,a3 が表示される 12. 終了 (教科書 P165 参照) 関数の動作おさらい
Prog Lec14-4 Copyright (C) 1999 – 2013 by Programming-0 Group 非線型方程式 f(x)=0 の解を求める方法 例題として、ニュートン法で の解 を求める ( ただし、 は与えられた定数、例えば 1,2 など ) 関数の例 1― ニュートン法 (p.178)
Prog Lec14-5 Copyright (C) 1999 – 2013 by Programming-0 Group 以後 x n につきステップ①②を繰り返 す x0x0 x1x1 f(x) 傾き f'(x 0 ) x 0 y 求める x x2x2 傾き f'(x 1 ) 傾き f'(x 2 ) x3x3 ニュートン法の原理 と x 軸との交点を 反復法によって求める ステップ① : 初期値 x 0 を与え、 点 (x 0,y 0 ) における y=f(x) の接線 ステップ② : 点 (x 1,y 1 ) における y=f(x) の接線 と x 軸との交点を求める ただし y 0 =f(x 0 ) ただし y 1 =f(x 1 ) と x 軸の交点 x 1 を求める x 0, x 1, x 2, x 3 … と だんだん f(x) の零点 ( 根になる点 ) に 近づいていく
Prog Lec14-6 Copyright (C) 1999 – 2013 by Programming-0 Group ニュートン法の原理まとめ 初期値 x 0 とし、そこでの接線と x 軸との交点を順次求めて行く ここで、 とする。 x0x0 x1x1 f(x) 傾き f'(x 0 ) x 0 y 求める x x2x2 傾き f'(x 1 ) 傾き f'(x 2 ) x3x3 求めた f(x k ) の値が一定の値(精 度)より小さければ終了 f(x 0 ) f(x 1 ) f(x 2 ) f( x k )<( 精度 )
Prog Lec14-7 Copyright (C) 1999 – 2013 by Programming-0 Group フローチャート x← x-f(x,a)/df(x) f(x,a)> 精度 はじめ x← 初期値 終り 真 偽 関数 f(x,a) fx← x*x-a return fx 関数 df(x) dfx←2*x return dfx 初期値は一般的に解より少し大きい値 a とする。 精度はマクロ EPS で与え、値は10 -6 とする また、一般的には「 f(x,a)< 精度」に関して、本当 は f(x,a) の絶対値を取る必要があるが、初期値を 解より大きい a としたので、負になることは考え ない 初期値は一般的に解より少し大きい値 a とする。 精度はマクロ EPS で与え、値は10 -6 とする また、一般的には「 f(x,a)< 精度」に関して、本当 は f(x,a) の絶対値を取る必要があるが、初期値を 解より大きい a としたので、負になることは考え ない dfx は、 fx つまり x 2 の 1 階微分だから 2x x 出力 読みこみ a← 定数
Prog Lec14-8 Copyright (C) 1999 – 2013 by Programming-0 Group プログラム #include #define EPS 1.0e-6 double f(double, double); double df(double); main() { double a, x, fx, dfx; printf("input a number : "); scanf("%lf",&a); x = a; printf("x(k-1) \ t \ tfx \ t \ tdfx \ t \ tx(k) \ t \ tf(x,%f)", a); while((fx = f(x,a)) > EPS ){ dfx = df(x); printf("%f \ t%f \ t%f",x,fx,dfx); x = x - fx/dfx; printf(" \ t%f \ t%12.10f \ n",x,f(x,a)); } printf("sqrt(%f) : %12.10f \ n",a,x); } #include #define EPS 1.0e-6 double f(double, double); double df(double); main() { double a, x, fx, dfx; printf("input a number : "); scanf("%lf",&a); x = a; printf("x(k-1) \ t \ tfx \ t \ tdfx \ t \ tx(k) \ t \ tf(x,%f)", a); while((fx = f(x,a)) > EPS ){ dfx = df(x); printf("%f \ t%f \ t%f",x,fx,dfx); x = x - fx/dfx; printf(" \ t%f \ t%12.10f \ n",x,f(x,a)); } printf("sqrt(%f) : %12.10f \ n",a,x); } double f(double x, double a) { double fx; fx = x*x - a; return fx; } double df(double x) { double dfx; dfx = 2.0*x; return dfx; } double f(double x, double a) { double fx; fx = x*x - a; return fx; } double df(double x) { double dfx; dfx = 2.0*x; return dfx; } タブ 代入してから比較 マクロで 精度を定義 /home/course/prog0/public_html/2013/lec/source/lec14-1.c 初期値
Prog Lec14-9 Copyright (C) 1999 – 2013 by Programming-0 Group 実行結果 std0dc0{s }1:./a.out input a number : 2 x(k-1) fx dfx x(k) f(x, ) sqrt( ) : std0dc0{s }2: std0dc0{s }1:./a.out input a number : 2 x(k-1) fx dfx x(k) f(x, ) sqrt( ) : std0dc0{s }2: 値が EPS (精度)以下に なった時点でループを終 了する x k での関数 f(x k ) の値。この 値が0になった時の x k が求 める値であるが、 f(x k ) は 完全に0にはならないの で、非常に小さい値 EPS 以下になった時の x k を解と する x k つまり x k-1 - fx/dfx x k-1
Prog Lec14-10 Copyright (C) 1999 – 2013 by Programming-0 Group 他の方程式 ( ) を解く 場合 double f(double x, double a) { double fx; fx = x*x*x – a ; return fx; } double df(double x) { double dfx; dfx = 3.0*x*x; return dfx; } double f(double x, double a) { double fx; fx = x*x*x – a ; return fx; } double df(double x) { double dfx; dfx = 3.0*x*x; return dfx; } main 関数での変更は最小 限で済む 簡単な変更で、もっと複 雑な方程式の解を求める ことも 可能 /home/course/prog0/public_html/2013/lec/source/lec14-2.c 2 dfx は、 fx つまり x 3 の 1 階微分だから 3x 2
Prog Lec14-11 Copyright (C) 1999 – 2013 by Programming-0 Group 関数の例 2 : 桁数を知る (1) 入力された数の桁数を返す関数を作る (例えば、「 」は8桁) 受け渡しの要件:1入力・1出力 入力:数⇨ int 型 出力:桁数⇨ int 型 関数名は桁数( digits )から digits とする。 digits のプロトタイプ宣言は以下のようになる。 int digits(int);
Prog Lec14-12 Copyright (C) 1999 – 2013 by Programming-0 Group 桁数を知る (2) 10 で割った答えが 0 でなければ、 2 桁以上の数 100(=10 10) で割った答えが 0 でなければ、 3 桁以上の数 1000(=100 10) で割った答えが 0 でなければ、 4 桁以上の数 … と割る数を順次 10 倍し、答えが 0 でない限り計算を続ける。 答えが初めて 0 になったときを見つける。 10 で割った答えが 0 でなければ、 2 桁以上の数 100(=10 10) で割った答えが 0 でなければ、 3 桁以上の数 1000(=100 10) で割った答えが 0 でなければ、 4 桁以上の数 … と割る数を順次 10 倍し、答えが 0 でない限り計算を続ける。 答えが初めて 0 になったときを見つける。 ループ回数 yx/y keta 引数に が渡された 時のループの様子 整数どうしの割り算は小数点 以下は切り捨てられる int digits(int x) { int keta = 1, y = 10; while((x / y) > 0){ y *= 10; keta++; } return keta; } int digits(int x) { int keta = 1, y = 10; while((x / y) > 0){ y *= 10; keta++; } return keta; } xをyで割った答えが0以外の間以下の処 理 yを10倍する ( つまり今度は一桁上を見 る) 桁数に1加える xをyで割った答えが0以外の間以下の処 理 yを10倍する ( つまり今度は一桁上を見 る) 桁数に1加える 右表はこの時点の値 初期値として 桁数=1,y=10 とする ループここまで 関数の値として桁数をリ ターン ループここまで 関数の値として桁数をリ ターン 数が一桁の場合は 一度もループに入らな い
Prog Lec14-13 Copyright (C) 1999 – 2013 by Programming-0 Group #include int digits(int); main() { int i, j; scanf("%d",&i); j = digits(i); printf("%d の桁数は %d です \ n",i,j); } int digits(int x) { int keta = 1, y = 10 ; while((x / y) > 0){ y *= 10; keta++; } return keta; } #include int digits(int); main() { int i, j; scanf("%d",&i); j = digits(i); printf("%d の桁数は %d です \ n",i,j); } int digits(int x) { int keta = 1, y = 10 ; while((x / y) > 0){ y *= 10; keta++; } return keta; } 実行結果 std1dc1{s }1:./a.out の桁数は 8 です std1dc1{s }2: 実行結果 std1dc1{s }1:./a.out の桁数は 8 です std1dc1{s }2: プログラムと実行結果 /home/course/prog0/public_html/2013/lec/source/lec14-3.c 43
Prog Lec14-14 Copyright (C) 1999 – 2013 by Programming-0 Group 関数の例 3 : ある桁の数を知る (1) 入力された数字の指定された桁の数を返す関数を作る (例えば、 の下から2桁目は2) 受け渡しの要件:2入力・1出力 二つの入力: データ ⇨ int 型 桁数 ⇨ int 型 結果(その桁の数) ⇨ int 型 関数名は get_1_digit とした。この関数のプロトタイ プ宣言は以下のようになる。 int get_1_digit(int,int); データ桁数
Prog Lec14-15 Copyright (C) 1999 – 2013 by Programming-0 Group ある桁の数を知る (2) 例えば の(下から)2桁目は2 % 100 = / 10 = 2 つまり 求める数 = ( データ % 10 桁数 ) / 10 ( 桁数ー1 ) 10 ( 桁数ー1 ) をどう作るか? ⇨ 10を(桁数ー1)回掛け合わせる(ループに て) 5 剰余算
Prog Lec14-16 Copyright (C) 1999 – 2013 by Programming-0 Group #include int get_1_digit(int, int); main() { int i, j = 2, result; scanf("%d",&i); result = get_1_digit(i, j); printf("%d の %d 桁目は %d です \ n",i,j,result); } int get_1_digit(int x, int pos) { int i, j, k = 1; for(i = 1 ; i < pos ; i++){ k *= 10; } j = x % (k * 10) / k; return j; } #include int get_1_digit(int, int); main() { int i, j = 2, result; scanf("%d",&i); result = get_1_digit(i, j); printf("%d の %d 桁目は %d です \ n",i,j,result); } int get_1_digit(int x, int pos) { int i, j, k = 1; for(i = 1 ; i < pos ; i++){ k *= 10; } j = x % (k * 10) / k; return j; } 実行結果 std1dc1{s }1:./a.out の 2 桁目は 2 です std1dc1{s }2: 実行結果 std1dc1{s }1:./a.out の 2 桁目は 2 です std1dc1{s }2: プログラムと実行結果 10(桁数ー1)のためのループ (データ%10桁数) / 10(桁 数ー1) の計算 (データ%10桁数) / 10(桁 数ー1) の計算 /home/course/prog0/public_html/2013/lec/source/lec14-4.c
Prog Lec14-17 Copyright (C) 1999 – 2013 by Programming-0 Group 10進数 : 10本指の人間が理解しやすい 14 (10) = 1×10 1 + 4×10 0 2進数 : コンピュータが理解出来る(スイッチの on/off として) 1110 (2) = 1×2 3 + 1×2 2 + 1×2 1 + 0×2 0 = 14 (10) X進数の「X]のことを基数と呼ぶ。 0からカウントアップし、基数になると桁上げが起こる。 2進数は0、1と来て、2になると桁上げが起こり10になる。 コンピュータの内部では、命令もデータも全て 0/1 の列( 2 進数)で表現さ れる → 詳細は後期、「コンピュータシステム概論」で学ぶ 32 ビット( 32 個の 0/1 の系列)で表現できる最大の数 2 32 = = 約 43 億 →138 年(一秒ごとにひとつカウントした場合) 例4の前に: 2 進数と 10 進数
Prog Lec14-18 Copyright (C) 1999 – 2013 by Programming-0 Group 関数の例4: 2進数から10進数への変換 (1) 入力された数字を2進数とみなして10進数に変換する プログラム(これまで作った関数を使う) 構成 main digits (桁数を求める) get_1_digit (数のうち一桁だけを取り出す) main() で行うこと: 0が入力されるまで無限ループで数を読み込む 2進数から10進数への変換(次ページ)
Prog Lec14-19 Copyright (C) 1999 – 2013 by Programming-0 Group 2進数から10進数への変換 (2) 各桁の重みと各桁の数をかけた物を加え合わす 例: (2進数) ⇨ 43 25*1+24*0+23*1+22*0+21*1+20*1 = 43 処理: keta = digits(data) total = 0; exp = 1; for(i = 1 ; i <= keta ; i++){ n = get_1_digit(data, i); total += n * exp; exp *= 2; } keta = digits(data) total = 0; exp = 1; for(i = 1 ; i <= keta ; i++){ n = get_1_digit(data, i); total += n * exp; exp *= 2; } 桁数を求める ( digits を使用 ) 合計を0にする。 桁の重みの初期値を20(=1)にする 桁数回ループして各桁について計算する その桁の数を求める ( get_1_digit を使用 ) 桁の重みと桁の数(0または1)を掛けて合計に足し 込む 次のループに備えて次の桁の重みを計算する(重みを 2倍する) ループここまで 桁数を求める ( digits を使用 ) 合計を0にする。 桁の重みの初期値を20(=1)にする 桁数回ループして各桁について計算する その桁の数を求める ( get_1_digit を使用 ) 桁の重みと桁の数(0または1)を掛けて合計に足し 込む 次のループに備えて次の桁の重みを計算する(重みを 2倍する) ループここまで nexptotal この場所での値 ループの様子 計算順序
Prog Lec14-20 Copyright (C) 1999 – 2013 by Programming-0 Group #include int digits(int); int get_1_digit(int, int); main() { int data, keta, i, n, exp, total; while(1){ /* データ読み込みとチェック */ printf(" 8桁以下の2進数を入力 ==> "); scanf("%d",&data); if (data == 0) exit(0); if (data > || data < 0){ printf(" 変換出来る範囲を越えています\ n"); continue; } /* 桁数計算 */ keta = digits(data); /* 10進数に変換 */ total = 0; exp = 1; for(i = 1 ; i <= keta ; i++){ n = get_1_digit(data, i); if((n != 0) && (n != 1)){ /* データチェック */ printf(" データが0か1ではありません\ n"); exit(8); } total += n * exp; exp *= 2; } printf(" 2進 : %d -> 10進 : %d \ n",data,total); } #include int digits(int); int get_1_digit(int, int); main() { int data, keta, i, n, exp, total; while(1){ /* データ読み込みとチェック */ printf(" 8桁以下の2進数を入力 ==> "); scanf("%d",&data); if (data == 0) exit(0); if (data > || data < 0){ printf(" 変換出来る範囲を越えています\ n"); continue; } /* 桁数計算 */ keta = digits(data); /* 10進数に変換 */ total = 0; exp = 1; for(i = 1 ; i <= keta ; i++){ n = get_1_digit(data, i); if((n != 0) && (n != 1)){ /* データチェック */ printf(" データが0か1ではありません\ n"); exit(8); } total += n * exp; exp *= 2; } printf(" 2進 : %d -> 10進 : %d \ n",data,total); } int digits(int x) { int keta = 1, k = 10; while((x / k) > 0){ k *= 10; keta++; } return keta; } int get_1_digit(int x, int pos) { int i, j, k = 1; for(i = 1 ; i < pos ; i++){ k *= 10; } j = x % (k * 10) / k; return j; } int digits(int x) { int keta = 1, k = 10; while((x / k) > 0){ k *= 10; keta++; } return keta; } int get_1_digit(int x, int pos) { int i, j, k = 1; for(i = 1 ; i < pos ; i++){ k *= 10; } j = x % (k * 10) / k; return j; } 右に続く⇨ ⇨左から続く 2進数 →10 進数変換プログラ ム /home/course/prog0/public_html/2013/lec/source/lec14-6{a,b,c,d}.c 8 lec14-6a.c が記載されたプログラム。 後は様々なバリエーション
Prog Lec14-21 Copyright (C) 1999 – 2013 by Programming-0 Group std1dc1{s }1:./a.out 8桁以下の2進数を入力 ==> 2進 : > 10進 : 43 8桁以下の2進数を入力 ==> 変換出来る範囲を越えています 8桁以下の2進数を入力 ==> -101 変換出来る範囲を越えています 8桁以下の2進数を入力 ==> データが0か1ではありません std1dc1{s }2: echo $status 8 std1dc1{s }3: std1dc1{s }1:./a.out 8桁以下の2進数を入力 ==> 2進 : > 10進 : 43 8桁以下の2進数を入力 ==> 変換出来る範囲を越えています 8桁以下の2進数を入力 ==> -101 変換出来る範囲を越えています 8桁以下の2進数を入力 ==> データが0か1ではありません std1dc1{s }2: echo $status 8 std1dc1{s }3: 実行例 9 cshの場合
Prog Lec14-22 Copyright (C) 1999 – 2013 by Programming-0 Group エラーチェック エラーチェックとは 予想される間違いを検出 すること 前述の例4のプログラムの場合、以下のような入力誤りが予想される 負の数が入力される(例:-101) 1 桁の数が入力される(例:8) エラーを検出した場合はエラーに応じた処理を行う(「エラー処理」と言 う) エラーの重大さによって、処理が異なることがある(例えば軽度なエラーは 処理を続行させ、重度なエラーは処理を中止するなど) 例4のプログラムの場合 負の数だった場合:「変換出来る範囲を越えています」と表示して再度データ入 力からやり直す 1桁の数だった場合、「データが適切ではありません」と表示してプログラムを 強制終了させる。(強制終了は次ページ参照) エラーチェックをしっかりしておくとプログラムの誤動作を未然に防ぐこと が出来る 7 6
Prog Lec14-23 Copyright (C) 1999 – 2013 by Programming-0 Group プログラムの強制終了 プログラムの強制終了の方法 をインクルードする。 exit ( 整数 ); で どこからでもプログラムを強制終了させることが出 来る。 プログラム実行後、この引数はシェル変数に渡される(例えば tcsh だ と「 $? 」、 csh だと 「 $status 」 というシェル変数に格納される) シェル変数の制約により渡せる引数は 0 ~ 255 までの整数のみに限られ る。 このようにプログラムからシェル変数に値が渡る事で、 値によってエラーの理由を知ることが出来る シェルスクリプトを使用して値によって動作を変える事が可能 例えば以下のような非常に簡単なプログラム ( exit(8); で強制終了) の実行終了後 $status を見ると8という数字が入っていることが分か る。 #include main() { exit(8); } #include main() { exit(8); } 実行結果 (csh の場合) std1dc1{s }1:./a.out std1dc1{s }2: echo $status 8 std1dc1{s }3: 実行結果 (csh の場合) std1dc1{s }1:./a.out std1dc1{s }2: echo $status 8 std1dc1{s }3: /home/course/prog0/public_html/2013/lec/source/lec14-5.c
Prog Lec14-24 Copyright (C) 1999 – 2013 by Programming-0 Group 補足:10進数から2進数への変換 なお、10進数から2進数への変換は以下のようなプ ログラムによって行う事が出来る。 #include main() { unsigned int data; int bin[32], i; printf(" 10進数を入力 ==> "); scanf("%u",&data); for(i = 0; i < 32; i++){ bin[31 - i] = data % 2; data /= 2; } for(i = 0; i < 32; i++){ printf("%1d",bin[i]); } printf(" \ n"); } #include main() { unsigned int data; int bin[32], i; printf(" 10進数を入力 ==> "); scanf("%u",&data); for(i = 0; i < 32; i++){ bin[31 - i] = data % 2; data /= 2; } for(i = 0; i < 32; i++){ printf("%1d",bin[i]); } printf(" \ n"); } #include main() { unsigned int data; int i; printf(" 10進数を入力 ==> "); scanf("%u",&data); for(i = 0; i < 32; i++){ printf("%1d",(data >> (31 - i)) & 1); } printf(" \ n"); } #include main() { unsigned int data; int i; printf(" 10進数を入力 ==> "); scanf("%u",&data); for(i = 0; i < 32; i++){ printf("%1d",(data >> (31 - i)) & 1); } printf(" \ n"); } 配列を使用 シフト演算 子を使用 /home/course/prog0/public_html/2013/lec/source/lec14-7{a,b,c}.c