240 likes | 421 Views
プログラミング入門 第1 4 回講義. 関数(その2) 関数の復習 (2) 関数の例1 ニュートン法 (4) 関数の例2 桁数 (11) 関数の例3 ある桁の数 (14) エラーチェックと強制終了 (17) 関数の例4 2進→10進変換 (20). マークのあるサンプルプログラムは /home/course/prog0/public_html/2013/lec/source/ 下に置いてありますから、各自自分のディレクトリに コピーして、コンパイル・実行してみてください. #include <stdio.h> double nijou(double);
E N D
プログラミング入門第14回講義 • 関数(その2) • 関数の復習(2) • 関数の例1 ニュートン法(4) • 関数の例2 桁数(11) • 関数の例3 ある桁の数(14) • エラーチェックと強制終了(17) • 関数の例4 2進→10進変換(20) マークのあるサンプルプログラムは /home/course/prog0/public_html/2013/lec/source/ 下に置いてありますから、各自自分のディレクトリに コピーして、コンパイル・実行してみてください
#include <stdio.h> 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
#include <stdio.h> 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 ; } 関数の動作おさらい • mainの最初の実行文から開始 • nijou(3.0)が評価される • 3.0が関数の仮引数xにコピーされ、関数nijouに実行が移る • 計算の結果、yに9.0が入り、「return y」で9.0がnijou関数の結果となる • nijou関数の結果がa1に代入される。 • 次にnijou(x)が評価される • x(2.0)が関数のxにコピーされ、関数nijouに実行が移る • 計算の結果yに4.0が入り、「return y」で4.0がnijou関数の結果となる • nijou関数の結果がa2に代入される • a3も同様に計算される • printfでa1,a2,a3が表示される • 終了 • (教科書P165参照)
関数の例1―ニュートン法(p.178) • 非線型方程式f(x)=0の解を求める方法 • 例題として、ニュートン法での解を求める (ただし、 は与えられた定数、例えば1,2など)
ニュートン法の原理 とx軸との交点を 反復法によって求める f(x) y 傾き f'(x0) ステップ①: 初期値x0 を与え、 点(x0,y0)におけるy=f(x) の接線 傾き f'(x1) 求めるx とx軸の交点x1を求める 傾き f'(x2) ただしy0=f(x0) ステップ② : 点(x1,y1)におけるy=f(x) の接線 とx軸との交点を求める 0 x x3 x2 x1 x0 x0, x1,x2 ,x3 …とだんだんf(x) の零点(根になる点)に近づいていく ただしy1=f(x1) 以後xnにつきステップ①②を繰り返す
ニュートン法の原理まとめ 初期値x0とし、そこでの接線とx軸との交点を順次求めて行く f(x) y 傾き f'(x0) f(x0) 傾き f'(x1) 求めるx ここで、 傾き f'(x2) f(x1) f(xk)<(精度) f(x2) 0 x x3 x2 x1 x0 とする。 求めたf(xk) の値が一定の値(精度)より小さければ終了
フローチャート はじめ 関数f(x,a) 関数df(x) 読みこみ a←定数 dfxは、fxつまり x2 の1階微分だから 2x x←初期値 dfx←2*x fx← x*x-a f(x,a)>精度 偽 return dfx 真 return fx x←x-f(x,a)/df(x) • 初期値は一般的に解より少し大きい値aとする。 • 精度はマクロEPSで与え、値は10-6とする • また、一般的には「f(x,a)<精度」に関して、本当はf(x,a)の絶対値を取る必要があるが、初期値を解より大きいaとしたので、負になることは考えない x出力 終り
#include <stdio.h> #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; } 初期値 タブ 代入してから比較 /home/course/prog0/public_html/2013/lec/source/lec14-1.c
xkでの関数f(xk)の値。この値が0になった時のxkが求める値であるが、 f(xk)は完全に0にはならないので、非常に小さい値EPS以下になった時のxkを解とする 実行結果 xkつまりxk-1 - fx/dfx std0dc0{s1000000}1: ./a.out input a number : 2 x(k-1) fx dfx x(k) f(x,2.000000) 2.000000 2.000000 4.000000 1.500000 0.2500000000 1.500000 0.250000 3.000000 1.416667 0.0069444444 1.416667 0.006944 2.833333 1.414216 0.0000060073 1.414216 0.000006 2.828431 1.414214 0.0000000000 sqrt(2.000000) : 1.4142135624 std0dc0{s1000000}2: xk-1 値がEPS (精度)以下になった時点でループを終了する
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関数での変更は最小限で済む 簡単な変更で、もっと複雑な方程式の解を求めることも可能 他の方程式( )を解く場合 2 dfxは、fxつまり x3 の1階微分だから3x2 /home/course/prog0/public_html/2013/lec/source/lec14-2.c
関数の例2:桁数を知る(1) • 入力された数の桁数を返す関数を作る(例えば、「12345678」は8桁) • 受け渡しの要件:1入力・1出力 • 入力:数 ⇨int型 • 出力:桁数 ⇨int型 • 関数名は桁数(digits)からdigitsとする。digitsのプロトタイプ宣言は以下のようになる。int digits(int);
桁数を知る(2) 10で割った答えが0でなければ、2桁以上の数 100(=1010)で割った答えが0でなければ、3桁以上の数 1000(=10010)で割った答えが0でなければ、4桁以上の数 … と割る数を順次10倍し、答えが0でない限り計算を続ける。 答えが初めて0になったときを見つける。 数が一桁の場合は一度もループに入らない int digits(int x) { int keta = 1 , y = 10; while((x / y) > 0){ y *= 10; keta++; } return keta; } 初期値として桁数=1,y=10とする 右表はこの時点の値 引数に12345678が渡された 時のループの様子 整数どうしの割り算は小数点 以下は切り捨てられる xをyで割った答えが0以外の間以下の処理 yを10倍する(つまり今度は一桁上を見る) 桁数に1加える ループここまで 関数の値として桁数をリターン
#include<stdio.h> 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{s1000000}1:./a.out 12345678 12345678の桁数は 8 です std1dc1{s1000000}2: 3 4 /home/course/prog0/public_html/2013/lec/source/lec14-3.c
関数の例3:ある桁の数を知る(1) • 入力された数字の指定された桁の数を返す関数を作る (例えば、654321の下から2桁目は2) • 受け渡しの要件:2入力・1出力 • 二つの入力: • データ ⇨int型 • 桁数 ⇨int型 • 結果(その桁の数) ⇨int型 • 関数名はget_1_digitとした。この関数のプロトタイプ宣言は以下のようになる。int get_1_digit(int,int); データ 桁数
ある桁の数を知る(2) • 例えば654321の(下から)2桁目は2 654321 % 100 = 21 21 / 10 = 2 • つまり 求める数 = (データ%10桁数)/10(桁数ー1) • 10(桁数ー1)をどう作るか? ⇨10を(桁数ー1)回掛け合わせる(ループにて) 剰余算 5
#include<stdio.h> 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{s1000000}1:./a.out 654321 654321の 2 桁目は 2 です std1dc1{s1000000}2: 10(桁数ー1)のためのループ (データ%10桁数)/10(桁数ー1) の計算 /home/course/prog0/public_html/2013/lec/source/lec14-4.c
例4の前に:2進数と10進数 • 10進数 : 10本指の人間が理解しやすい 14(10) = 1×101+4×100 • 2進数 : コンピュータが理解出来る(スイッチのon/offとして) 1110(2) = 1×23+1×22+1×21+0×20 = 14(10) • X進数の「X]のことを基数と呼ぶ。 • 0からカウントアップし、基数になると桁上げが起こる。 • 2進数は0、1と来て、2になると桁上げが起こり10になる。 • コンピュータの内部では、命令もデータも全て0/1の列(2進数)で表現される→詳細は後期、「コンピュータシステム概論」で学ぶ • 32ビット(32個の0/1の系列)で表現できる最大の数232=4294967296=約43億→138年(一秒ごとにひとつカウントした場合)
関数の例4:2進数から10進数への変換(1) • 入力された数字を2進数とみなして10進数に変換するプログラム(これまで作った関数を使う) • 構成 • main • digits(桁数を求める) • get_1_digit(数のうち一桁だけを取り出す) • main()で行うこと: • 0が入力されるまで無限ループで数を読み込む • 2進数から10進数への変換(次ページ)
2進数から10進数への変換(2) 計算順序 • 各桁の重みと各桁の数をかけた物を加え合わす 例:101011(2進数) ⇨43 25*1+24*0+23*1+22*0+21*1+20*1 = 43 • 処理: • 桁数を求める(digitsを使用) • 合計を0にする。 • 桁の重みの初期値を20(=1)にする • 桁数回ループして各桁について計算する • その桁の数を求める(get_1_digitを使用) • 桁の重みと桁の数(0または1)を掛けて合計に足し込む • 次のループに備えて次の桁の重みを計算する(重みを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; } この場所での値
#include<stdio.h> #include<stdlib.h> 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 > 11111111 || 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); } } 2進数→10進数変換プログラム ⇨左から続く 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; } 8 lec14-6a.cが記載されたプログラム。後は様々なバリエーション /home/course/prog0/public_html/2013/lec/source/lec14-6{a,b,c,d}.c 右に続く⇨
実行例 cshの場合 std1dc1{s1000000}1: ./a.out 8桁以下の2進数を入力==> 101011 2進 : 101011 ->10進 : 43 8桁以下の2進数を入力==> 101010101 変換出来る範囲を越えています 8桁以下の2進数を入力==> -101 変換出来る範囲を越えています 8桁以下の2進数を入力==> 10123 データが0か1ではありません std1dc1{s1000000}2: echo $status 8 std1dc1{s1000000}3: 9
エラーチェック 6 • エラーチェックとは 予想される間違いを検出 すること • 前述の例4のプログラムの場合、以下のような入力誤りが予想される • 負の数が入力される(例:-101) • 1桁の数が入力される(例:8) • エラーを検出した場合はエラーに応じた処理を行う(「エラー処理」と言う) • エラーの重大さによって、処理が異なることがある(例えば軽度なエラーは処理を続行させ、重度なエラーは処理を中止するなど) • 例4のプログラムの場合 • 負の数だった場合:「変換出来る範囲を越えています」と表示して再度データ入力からやり直す • 1桁の数だった場合、「データが適切ではありません」と表示してプログラムを強制終了させる。(強制終了は次ページ参照) • エラーチェックをしっかりしておくとプログラムの誤動作を未然に防ぐことが出来る 7
プログラムの強制終了 • プログラムの強制終了の方法 • <stdlib.h>をインクルードする。 • exit(整数);でどこからでもプログラムを強制終了させることが出来る。 • プログラム実行後、この引数はシェル変数に渡される(例えば tcsh だと「$?」、 csh だと「$status」というシェル変数に格納される) • シェル変数の制約により渡せる引数は0~255までの整数のみに限られる。 • このようにプログラムからシェル変数に値が渡る事で、 • 値によってエラーの理由を知ることが出来る • シェルスクリプトを使用して値によって動作を変える事が可能 • 例えば以下のような非常に簡単なプログラム(exit(8);で強制終了)の実行終了後$statusを見ると8という数字が入っていることが分かる。 #include <stdio.h> #include <stdlib.h> main() { exit(8); } 実行結果(cshの場合) std1dc1{s1000000}1: ./a.out std1dc1{s1000000}2: echo $status 8 std1dc1{s1000000}3: /home/course/prog0/public_html/2013/lec/source/lec14-5.c
補足:10進数から2進数への変換 なお、10進数から2進数への変換は以下のようなプログラムによって行う事が出来る。 #include<stdio.h> 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<stdio.h> 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