/* * TITLE * * 2021/09/17 FILENAME * * 代入文 * 整数型変数 ( a, b, c ) を三つ宣言する * 整数型変数 a, b にそれぞれ、123, 4 を代入する * 整数型変数 c に a と b の和を代入する * 整数型変数 c の値を画面に出力する * * printf を使わずに、整数値の出力 * 概論 A 導入した s_print.h を利用 * s_print.h をみると、中で、printf を利用している * <= 書式指定の説明 */ #include <stdio.h> #include "s_print.h" /* s_print_int 関数(整数値出力)のため.. */ /* * main */ int main( int argc, char *argv[] ) { /* 局所変数(ローカル)変数[auto 変数] の宣言 */ /* ブロック ( { ? } ) の先頭で、行い、 そのブロック内だけで有効な変数の型宣言を行う */ /* 型宣言を行う事によって、自動的に、変数が作られる */ /* 整数型変数 a の宣言 */ int a; /* 整数型変数 b の宣言 */ int b; /* 整数型変数 c の宣言 */ int c; /* int a, b, c; */ /* 整数型変数 a に 123 を代入 */ a = 123; /* 代入文 : 変数名 = 式 */ /* 整数型変数 b に 4 を代入 */ b = 4; /* 整数型変数 c に、変数 a と 変数 b の和を代入 */ c = a + b; /* c <- a + b => 123 + 4 => 127 */ /* 代入文の右辺の式は、「評価(値の計算)された後」に 計算の結果(の値)が左辺に指定した変数名を持つ変数に 代入される 変数の値は、その時点での値が利用される */ /* a = 321; ここで a を変更しても、c の値は 127 のまま */ /* 整数型変数 c の値を画面に出力 */ s_print_string ( "変数 c の値は " ); s_print_int ( c ); /* 整数値を出力する */ s_print_string ( " です。\n" ); /* 手続き型言語 変数の値を、どんどん変更していって、 最終的に欲しい、結果を持つ変数をつくりだし、 (必要に応じて..) その変数の値を出力する プログラムの実行 変数の値が、どんどん書き換わる */ return 0; }
/* * TITLE * * 20211001-01-QQQQ.c * * コンパイル : * cc -c 20211001-01-QQQQ.c * cc -o 20211001-01-QQQQ.exe 20211001-01-QQQQ.o * 実行 : * ./20211001-01-QQQQ.exe * * コマンドラインで指定された整数値の総和を計算する * コマンドライン引数で指定したデータは * 文字列 * 「123」と指定をしても、入力されるのは、文字列の "123" * atoi ( Ascii to Int ) 関数が、 * 整数を表す文字列を、整数値にかえてくれる * atoi ( "123" ) -> 123 * * コマンドライン引数で指定された(整数値を表すであろう..)文字列 * を atoi を使って、整数値に変換して、加える * 引数の個数は、argc で調べる * 個数が不定なので、while 構文を利用して、繰り返す */ #include <stdio.h> #include <stdlib.h> /* atoi のために.. */ /* * main */ int main ( int argc, char *argv[] ) { /* argc, argv を void にすると、コマンドライン引数が 指定されていても、それを参照する事ができない */ int sum = 0; /* 総和の値を保持する変数 */ /* 総和: sum = 0; while ( まだ、要素がある ) { sum = sum + 個々の要素 次の要素を参照する準備 } sum = atoi(argv[1]) + atoi(argv[2]) + .. + atoi(argv[argc-1]) sum = 0 sum = sum + atoi(argv[1]) sum = sum + atoi(argv[2]) .. sum = sum + atoi(argv[argc-1]) sum = 0 n=1 sum = sum + atoi(argv[n]) n = n + 1 sum = sum + atoi(argv[n]) n = n + 1 .. sum = sum + atoi(argv[n]) n = n + 1 n < argc ( 1 ? argc - 1 ) */ int n = 1; /* 現在参照している引数の番号 */ while ( n < argc ) { /* 引数のある限り */ /* その引数の文字列を整数値に変換し、加える */ sum = sum + atoi(argv[n]); /* 次の引数を参照するために、参照する番号を増やす */ n = n + 1; } /* 結果を出力 */ printf ( "引数で指定された整数値並びの総和は %d です。\n", sum ); return 0; }
/* * 20211008-01-QQQQ.c * * 一つ浮動小数点数値をキーボードから入力し、その立方根を出力する * 手段としては、「二分法」を使う * * コンパイル : * cc -c 20211008-01-QQQQ.c * cc -o 20211008-01-QQQQ.exe 20211008-01-QQQQ.o * 実行 : * ./20211008-01-QQQQ.exe * */ #include <stdio.h> /* * */ #define EPSILON 0.00000001 /* 誤差幅 */ /* α > 1 を入力した時に、αの立方根 (β = α^(1/3)) が、 区間 [ a, b ] の中で、|b-a| < EPSILON となる a, b をもとめ その中点 (a+b)/2 を答えとする 一番最初の [a,b] f(x) = x^3 - α 欲しい答えは f(β) = 0 となるβだが、 f(0) = 0^3 - α = - α < 0 f(α) = α^3 - α = α(α^2-1) > 0 f(0) < f(β)=0 < f(α) [0,α] に必ず答えがある !! 数学的に示す !! α>1 の時は、 !! α^(1/3) > 1 !! なので、 !! [1,α]の中にあるが !! [1,α] \subset [0,α] !! なので、 !! [1,α] の中に答えがある !! ならば、[0,α]に答えがあるといっても、嘘ではない */ /* * double regula_falsi_cubic_root ( double a, double min, double max ) * double a 立方根の元になる数(正を仮定している) * double min, max 根の入る区間の範囲 * return a 立方根 * 二分法により、a の立方根を求める * 0 < min < a の立方根 < max */ double regula_falsi_cubic_root ( double a, double min, double max ) { double mid; /* 答えを含む区間の中点 */ /* 答えが、区間 [ min, max ] に入っているとする max-min > EPSILON の間はまだ、十分な精度になっていない */ while ( max - min > EPSILON ) { /* 精度が不十分 */ mid = (max + min) / 2.0; /* 区間の中点を求める */ /* min が解のどちら側にあるかを調べ.. それに併せて区間を調整 */ /* f(x)=x^3-a */ if ( mid * mid * mid - a < 0.0 ) { /* f(mid) の符号を確認 */ /* 中点は解の左にあった ( min を更新する ) */ min = mid; } else { /* 中点は解の右にあった ( max を更新する ) */ max = mid; } } /* [min, max] = [min, mid][mid, max] 右なら [min,mid] 左なら [mid,max] の中を探す ( 二分法 ) この作業を行うと、探す範囲の幅が 繰り返すたびに、半分になる # 幅が小さく (数学的には..) 0 に収束する */ /* 区間は十分に狭いので.. 近似値として、区間の中点を答える */ return (min+max)/2.0; } /* * double cubic_root ( double a ) * double a 立方根の元になる数 * return a 立方根 * a の立方根を求めて結果として返すが、 * 計算の基本は、regula_falsi_cubic_root にまかせる * ここでは、計算の正規化を行う */ /* α > 1 の時には、[0,1] からはじめればよい そうでない時にどうするか ? α > 1 1 > α > 0 α^(1/3) = 1/((1/α)^(1/3)) α' = 1/α α' > 1 なので α' の立方根(β')がもとめられる β = 1/β'とすれば、これが求める答え => α > 0 答えが求められる事がわかった 0>α => αの立方根は、(-α)の立方根に -1 をかけたもの */ double cubic_root ( double a ) { if ( a < 0.0 ) { /* a が負の数ならば.. */ /* -a の立方根を計算し、その結果の負数を返す */ /*「-a」 は 「(-1.0) * a」 の省略記法 */ return - cubic_root ( -a ); } else if ( a < 1.0 ) { /* a が 1.0 未満なら */ return 1/cubic_root ( 1/a ); /* 立方根は a と 1.0 の間にある */ } else { /* そうでなければ.. */ return regula_falsi_cubic_root ( a, 0, a ); /* 立方根は 1.0 と a の間にある */ /* [1,a] の間 */ } } /* * main */ int main( double argc, char *argv[] ) { double d; /* 入力した、立方根を計算したい数値を納める変数 */ double r; /* 立方根の計算結果 */ /* 実数値をキーボードから入力する */ printf ( "実数値を一つ入力してください : " ); scanf ( "%lf", &d ); /* 変数 d に入っている実数値の立方根を求めて、変数 r に代入 */ r = cubic_root ( d ); /* d の立方根をもとめて、r に代入 */ printf ( "%f の立方根は %f です。\n", d, r ); printf ( "誤差は %f - %f^3 = %f になります。\n", d, r, d - r * r * r ); return 0; }
#include <stdio.h> /* 二つの整数値をキーボードから入力して、 その和を計算して、出力 */ int main(int argc, char *argv[]) { int a; /* int a,b; */ /* 加えられる数を保存する変数 */ int b; /* 加える数を保存する変数 */ int wa; /* 和を計算した結果を別の変数に代入 */ /* 入力 */ printf ( "加えられる整数値を入力してください : " ); scanf ( "%d", &a ); /* 入力を行う関数 */ /* 書式 : 入力された(文字列を)どの型の値とみなすか */ /* %d : 入力された文字列を整数値としてとりこむ */ /* 入力する文字列の形式を指定する */ /* %d : 入力された文字列は、整数型として適切な表現になっている */ /* 具体例 : 123.45 => 123 しかとりこまない.. */ /* !! 書式指定により、表現の形式と型の両方が指定される */ /* 変数の指定 : 単純変数への代入だけ */ /* 変数名と、その前に「&」を付ける */ printf ( "加える整数値を入力してください : " ); scanf ( "%d", &b ); /* 処理 */ wa = a + b; /* a と b の和を計算して、その値を変数 w に代入 */ /* 出力 */ printf ( "%d と %d の和は %d になります。\n", a, b, wa ); return 0; }
#include <stdio.h> int main(int ac, char *av[] ) { while ( 1 > 0 ) { printf ( "実行する命令\n" ); } return 0; }
#include <stdio.h> int main(int ac, char *av[] ) { while ( 1 < 0 ) { printf ( "実行する命令\n" ); } return 0; }
#include <stdio.h> int main(int ac, char *av[] ) { int v; /* while の中で、値変更し、 条件式が、最初は真だが、 いつかは偽になって、 while 構文が終了するようにする => ループの制御変数 while 構文 : 繰り返し(loop:ループ) 繰り返しがおきるか、とまるかは、 その変数の値で決まる その変数が、繰り返しを制御しているようにみえる */ v = 0; while ( v < 10 ) { printf ( "実行する命令\n" ); v = v + 1; /* 0 -> 1 -> 2 ... -> 10 */ /* v = v - 1; 0 -> -1 -> -2 ,,, */ } /* while 構文の命令が終了した後では、 while 構文の「条件式」は、「偽」になっている 実際には、v = 10 となっていて v < 10 は 10 < 10 なので偽 # ここでは、v = 10 とことが保証 # <= この結果を期待して while 構文を使う場合もある */ return 0; }
#include <stdio.h> int main(int argc, char *argv[] ) { /* ./p-005.exe abc 12345 と実行する事を想定 */ printf ( "argc = %d\n", argc ); /* 4 となるはず */ printf ( "argv[1] = %s\n", argv[1] ); /* abc となるはず */ /* %s : 文字列を出力する書式 %d : 整数値 %f : 浮動小数点数 ( 入力[scanf] の時には %lf ) %c : 文字 */ printf ( "argv[2] = %s\n", argv[2] ); /* 12345 となるはず */ printf ( "argv[3] = %s\n", argv[3] ); /* @@@ となるはず */ printf ( "argv[0] = %s\n", argv[0] ); /* 実行したコマンド */ /* ./p-005.exe となる */ return 0; }
#include <stdio.h> #include <stdlib.h> /* 標準関数 atoi の宣言 */ /* atoi 関数が、(整数値を表す)文字列を、相当する整数値に 変換してくれる */ int main(int argc, char *argv[]) { /* atoi 関数の使い方 */ printf ( "%s -> %d になる\n", "123", atoi ( "123" ) ); /* 123 -> 123 */ printf ( "%s vs %d になる\n", "123" + 1, atoi ( "123" ) + 1 ); /* 23 vs 124 */ return 0; }
前回の内容 scanf scanf with format cf. printf : print with format 書式付きの出力 書式付きの入力 printf/scanf : 書式という共通性 / I/O (入力/出力) printf : すごくやくにたつし、ぜひ、常用してほしい sacnf : printf と同じくらい、強力だが、 色々ときをつける事があって、扱いが難しい => つかうなら限定した形でなれ なれてきたら、色々な機能を利用してほしい ! 失敗の影響力が違う ! => printf の失敗して、表示がおかしくなるだけ ! scanf の失敗 ! 入力なので、この後のプログラムの振る舞いに影響 ! (「ポインター」を扱うので、影響の仕方が特殊) scanf ( 書式, 入力するデータを保存する変数の指定 ) while 構文 形式: while ( 条件式 ) { 繰り返す命令 } 意味: 「条件式」が成立(真である)している間、「繰り返す命令」を繰り返す 流れ: まず、条件をチェック、 偽であれば、それでなにもしない 真であれば、「繰り返す命令」を実行する その後にまた、条件をチェックする 以下、上と同様 !! 条件のチェック回数は、実行回数 + 1 条件を毎回チェックするが、 条件の内容が変化していなければ、 一度もやらない ( いきなり偽 ) か、 無限に繰り返す ( 最初に真で、その後も真のまま ) 普通は、 条件式の中に、変数を含め、 「繰り返す命令」の中で、 その変数の値が(いつかは..)変化するようにする N 回繰り返すというイデオム int v; v = 0; while ( v < N ) { 繰り返す命令 <= N 回数繰り返される v = v + 1; } v = 1; while ( v <= N ) { 繰り返す命令 <= N 回数繰り返される v = v + 1; } cf. 概論 A では、再帰呼び出しを利用して繰り返しを実現 while 構文 : 効率が良い <= 原理から、「代入文」が不可欠 コマンドライン引数 main 関数 ( 概論 A は、void : 引数なし.. ) 実は、(いくつか..) 引数をもっている 最初の二つ int argc; コマンドライン引数の個数 + 1 char *argv[]; コマンドライン引数の文字列そのもの => この表現の意味は、後日説明 導入の段階では、使い方だけ コマンドライン引数 コマンドを実行する時に、実行ファイル名の後に指定する文字列 コマンドライン引数は空白文字で区切られている (間に空白文字[列]をおいて) 複数指定できる 例: ./p-005.exe abc 12345 @@@ ^^^^^^^^^^^ ^^^ ^^^^^ ^^^ 1 2 3 実行ファイル名 argc -> 4 argv[1] -> "abc" argv[2] -> "12345" argv[3] -> "@@@" argv[0] -> "./p-005.exe" (実行した命令) => データを入力する時に、 (scanf / getchar() 等の) 入力関数を用いて.. 入力してもよいが、 最初から、コマンドライン引数で、データを入力するというアプローチが便利 多用する.. 課題状況 2021/10/08 20211008-01 20211008-02 2021/10/01 20211001-01 済 20211002-02 <= ファイル I/O 2021/09/24 20210924-01 済 20210924-02 済 20210924-03 <= ファイル I/O 2021/09/17 20210917-01 済 20210917-02 済 20210917-03 済 20210917-04 == 数学の問題を解く ( 関数の値を求める ) 例: 関数 f(x) が f(x)=x^2 定積分 : f(x) の [0,2] の間、積分した結果を求める 解析的に解く 不定積分 F(x) を求める、 f(x) = x^2 F(x) = (1/3)x^3 + C ( C は積分定数 ) F(2)-F(0) = (1/3) [ 2^3 - 0^3 ] = 8/3 数学科が数学で学ぶのはこちら => 答えが正確にもとめられる ( よい方法.. ) <= この方法は、不定積分関数を求めることができないとダメ 不定積分関数が不明な場合 例 : 統計学で利用するガウス分布 e^(x^2) 数値的に解く 工学の学科の人が学ぶ方法 不定積分が直接(数学的に..)できない場合も、 近似値を計算する事ができる 誤差を含むけれど、応用範囲が広い 二分法 連続関数の中間値の定理 関数 f(x) が、区間 [a,b] で連続とし、 値 y0 f(a) < y0 < f(b) : y0 が中間(f(a)とf(b))の値 => ある c ( a < c < b ) があり f(c) = y0 なる | / y0|-* |/| +--------- a c b 二分法 f(x) が区間 [a,b] 連続とし、 y0 が区間 [f(a),f(b)] の中にある このとき、 f(c) = y0 となる c を [a,b] の中から探したい !! 中間値の定理からこのような c の存在が保証されている !! これから、答えを「探す」 !! 「探す」時に、「答えが『有る』という保証」がないと困っちゃう !! => 「困っちゃう」=もし、なかったら、いつまでたっても、終わらない !! x,y,z が整数 n > 2 整数の時に !! x^n + y^n = z^n !! となる x,y,z,n の組は存在しない !! 証明されるまえは、 !! このような組が存在するかもしれない !! プログラムで、しらみつぶしを試す !! => 結果的に終わらない !! 探すプログラムを作る時には、 !! 答えがある事が保証されていないと、 !! プログラムが終了しない時にこまる !! => 答えの「存在の証明」が不可欠 !! => 「存在証明」は数学でしかできない 二分法 0. m =(a+b)/2 1. [a,b] が十分に狭い場合 ( ε以下 ) には、 答えとして、m を近似値とする 2. f(m) を計算する 3. もし、 f(m)<y0 改めて区間を [a,m] とする そうでなければ、 区間を [m,b] とする 4. 0. に戻る | / <= f(m) > y0 | / y0 | / | / | / +--------------- a m b f(m) > y0 => [a,b] ではなく、[a,m] の間にある f(m) < y0 => [a,b] ではなく、[m,b] に間にある もともとの区間に対し、 その半分の区間を考え、 答えが、その二つの区間のどちらか一方にある ということを調べ、 答えのある範囲を狭めてゆく => 二分法 二分法を利用すると、 (少なくても一つの答えが含まれている区間がわかりさえすれば..) 連続関数の 0 点の答えを数値計算でもとめる事ができ 要求される条件 その関数がその区間で連続である 例: 5 次以上の代数方程式は、公式が存在しない 一般的は解析的には解けない 数値計算(二分法をつかえば..)を使えば、 このような場合でも、近似値が求められる
課題プログラム内の「/*名前:ここ*/」の部分を書き換え「/*この部分を完成させなさい*/」の部分にプログラムを追加して、プログラムを完成させます。
Download : 20211008-01.c
/* * 20211008-01-QQQQ.c * * 一つ浮動小数点数値をキーボードから入力し、その立方根を出力する * 手段としては、「二分法」を使う * * コンパイル : * cc -c 20211008-01-QQQQ.c * cc -o 20211008-01-QQQQ.exe 20211008-01-QQQQ.o * 実行 : * ./20211008-01-QQQQ.exe * */ #include <stdio.h> /* * */ #define EPSILON 0.00000001 /* 誤差幅 */ /* * double regula_falsi_cubic_root ( double a, double min, double max ) * double a 立方根の元になる数(正を仮定している) * double min, max 根の入る区間の範囲 * return a 立方根 * 二分法により、a の立方根を求める * 0 < min < a の立方根 < max */ double regula_falsi_cubic_root ( double a, double min, double max ) { double mid; /* 答えを含む区間の中点 */ while ( max - min > EPSILON ) { /* 精度が不十分 */ mid = (max + min) / 2.0; /* 区間の中点を求める */ /* min が解のどちら側にあるかを調べ.. それに併せて区間を調整 */ /* f(x)=x^3-a */ if ( mid * mid * mid - a < 0.0 ) { /* f(mid) の符号を確認 */ /* 中点は解の左にあった ( min を更新する ) */ /* ** この部分を完成させなさい */ } else { /* 中点は解の右にあった ( max を更新する ) */ max = mid; } } /* 区間は十分に狭いので.. 近似値として、区間の中点を答える */ return (min+max)/2.0; } /* * double cubic_root ( double a ) * double a 立方根の元になる数 * return a 立方根 * a の立方根を求めて結果として返すが、 * 計算の基本は、regula_falsi_cubic_root にまかせる * ここでは、計算の正規化を行う */ double cubic_root ( double a ) { if ( a < 0.0 ) { /* a が負の数ならば.. */ /* -a の立方根を計算し、その結果の負数を返す */ /*「-a」 は 「(-1.0) * a」 の省略記法 */ /* ** この部分を完成させなさい */ } else if ( a < 1.0 ) { /* a が 1.0 未満なら */ /* ** この部分を完成させなさい */ /* 立方根は a と 1.0 の間にある */ } else { /* そうでなければ.. */ return regula_falsi_cubic_root ( a, 1.0, a ); /* 立方根は 1.0 と a の間にある */ } } /* * main */ int main( double argc, char *argv[] ) { double d; /* 入力した、立方根を計算したい数値を納める変数 */ double r; /* 立方根の計算結果 */ /* 実数値をキーボードから入力する */ printf ( "実数値を一つ入力してください : " ); scanf ( "%lf", &d ); /* 変数 d に入っている実数値の立方根を求めて、変数 r に代入 */ /* ** この部分を完成させなさい */ printf ( "%f の立方根は %f です。\n", d, r ); printf ( "誤差は %f - %f^3 = %f になります。\n", d, r, d - r * r * r ); return 0; }
12.34
$ ./20211008-01-QQQQ.exe 実数値を一つ入力してください : 12.340000 12.340000 の立方根は 2.310850 です。 誤差は 12.340000 - 2.310850^3 = -0.000000 になります。 $
Download : 20211008-02.c
/* * 20211008-02-QQQQ.c * * 関数 sin(x) の区間 [0,π/4] の定積 * * コンパイル : * cc ' -c 20211008-02-QQQQ.c * cc -o 20211008-02-QQQQ.exe 20211008-02-QQQQ.o * 実行 : * ./20211008-02-QQQQ.exe * */ #include <stdio.h> #include <math.h> /* 数学的関数 sin を利用するので.. */ /* * リーマン積分を利用する */ #define FRACTIONAL 1000.0 /* 区間の等分数 */ /* * f(x)=sin(x) */ double f(double x) { /* * 引数 x に対して、x の 正弦値 sin(x) を値として返す関数 */ /* ** この部分を完成させなさい */ } /* reman_sum ( double min, double max, double delta ) S_1 〜 S_{n}^1 の和を計算する */ double reman_sum ( double min, double max, double delta ) { double sum = 0.0; /* 総和の計算結果 */ double x = min; /* 区分点の x 座標 ( 最初は min の値 ) */ while ( x < max ) { /* x の値が区間内ならば.. */ /* 短冊の面積を計算し、加える */ /* ** この部分を完成させなさい */ /* x の座標を次の点に移動させる */ x = x + delta; } /* この時点で sum には積分値が入っている */ return sum; } /* * リーマン積分 * * 関数の積分値が、小さな幅の短冊の面積の和で近似できる事を利用 * * solove_reaman ( double min, double max ) * */ double solve_reman ( double min, double max ) { /* min から max までを積分 基本は reman_sum に任せる */ return reman_sum ( min, max, (max-min)/FRACTIONAL ); } /* * main 関数 */ int main ( void ) { printf ( "関数 f(x)=sin(x) を区間[0,π/4]で数値定積分する。\n" ); printf ( "リーマンの定義に従って計算した答えは %f になりました。\n", solve_reman ( 0.0, M_PI/4.0 ) ); printf ( "解析的な計算の結果は 1-√2/2 なので、誤差は %f になり、答えに近い事が分ります\n", solve_reman ( 0.0, M_PI/4.0 ) - (1.0-sqrt(2.0)/2.0) ); return 0; }
$ ./20211008-02-QQQQ.exe 関数 f(x)=sin(x) を区間[0,π/4]で数値定積分する。 リーマンの定義に従って計算した答えは 0.293171 になりました。 解析的な計算の結果は 1-√2/2 なので、誤差は 0.000278 になり、答えに近い事が分ります $