Download : sample-001.c ( SJIS 版 )
/* * CDATE sample-001.c */ /* * 二分法による代数方程式の解法 * * 利用方法 * コンパイル * cc -I../../include -o BASENAME.exe sample-001.c * 実行 * BASENAME * * 代数方程式 * f(x) = (x-1)(x+1)(x-2) = x^3+2x^2-x-2 = 0 * を「数値的」に解く * */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * ε : EPSILON (誤差限界:これより小さい時は同じと見做す)の定義 */ #define EPSILON 0.000001 /* define でε(EPSILON)を定義 */ /* 計算結果の精度の尺度になる */ /* 残念ながら「0」にはできない */ /* 今回は 0.00001 だが、もちろん、 0.01 とか 0.00000001 にしてもよい */ /* * f(x) = x^3+2x^2-x-2 */ double f(double x) { return x * x * x + 2 * x * x - x - 2; } /* * 二分法で、方程式 f(x)=0 の解 (x0) を求める * * min < x0 < max であり f(min)<0, 0<f(max) を仮定する * * solove_binary ( double min, double max ) * */ double solve_binary ( double min, double max ) { /* f(min) < 0 < f(max) (仮定) なので、答えは [min,max] の中にある max-min < EPSILON なら、十分に精度が得られたとする そうでなければ、中点 (mid = (min+max)/2 ) での f の値 ( f(mid) ) の符号をみて、 正なら、答えは [min,mid] そうでなければ [mid,max] にある */ if ( (max-min) < EPSILON ) { /* 十分に狭い範囲にできた */ return (max+min)/2.0; /* 中点を答えにする */ } else if ( f((max+min)/2.0) > 0 ) { /* まだ大きい */ return solve_binary ( min, (max+min)/2.0 ); } else { return solve_binary ( (max+min)/2.0, max ); } } /* * main 関数 */ int main ( void ) { s_print_string ( "方程式 f(x)=x^3+2x^2-x-2=0 の解を、区間[0,3]から探す\n" ); s_print_string ( "f(0)=-2 < 0, f(3)=40 > 0 なので、[0,3] の中に解がある\n" ); s_print_string ( "二分法で得られた答えは : " ); s_print_double ( solve_binary ( 0.0, 3.0 ) ); s_print_string ( "になりました。\n" ); s_print_string ( "答えを代入すると " ); s_print_double ( f( solve_binary ( 0.0, 3.0 ) ) ); s_print_string ( " なので、ほぼ答えに近い事が分ります\n" ); return 0; }
$ ./sample-001.exe 方程式 f(x)=x^3+2x^2-x-2=0 の解を、区間[0,3]から探す f(0)=-2 < 0, f(3)=40 > 0 なので、[0,3] の中に解がある 二分法で得られた答えは : 1.000000になりました。 答えを代入すると 0.000001 なので、ほぼ答えに近い事が分ります $
Download : sample-002.c ( SJIS 版 )
/* * 2015/06/26 sample-002.c */ /* * リーマン積分 * * 利用方法 * コンパイル * cc -I../../include -o BASENAME.exe sample-002.c * 実行 * ./BASENAME.exe * * 定積分 * \int_0^1 x^2 dx * を「数値的」に解く * */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * */ #define FRACTIONAL 1000 /* 区間の等分数 */ /* * f(x)=x^2 */ double f(double x) { return x * x; } /* reman_sum ( int i, int n, double min, double max ) S_i 〜 S_{n^1} の和を計算する */ double reman_sum ( int i, int n, double min, double max ) { if ( i < n ) { /* まだ計算が必要 */ return reman_sum ( i + 1, n, min, max ) + f(min+i*(max-min)/n)*(max-min)/n; } else { /* もう、全て計算した */ return 0.0; /* 残る結果は 0 になる */ } } /* * リーマン積分 * * min < x0 < max であり f(min)<0, 0<f(max) を仮定する * * solove_reaman ( double min, double max ) * */ double solve_reman ( double min, double max ) { /* min から max までを積分 基本は reman_sum に任せる */ return reman_sum ( 0, FRACTIONAL, min, max ); } /* * main 関数 */ int main ( void ) { s_print_string ( "関数 f(x)=x^2 を区間[0,1]で数値定積分する。\n" ); s_print_string ( "リーマンの定義に従って計算した答えは : " ); s_print_double ( solve_reman ( 0.0, 1.0 ) ); s_print_string ( "になりました。\n" ); s_print_string ( "解析的な計算の結果は 1/3 なので、誤差は " ); s_print_double ( solve_reman ( 0.0, 1.0 ) - 1.0/3.0 ); s_print_string ( " になり、ほぼ答えに近い事がわかります\n" ); return 0; }
$ ./sample-002.exe 関数 f(x)=x^2 を区間[0,1]で数値定積分する。 リーマンの定義に従って計算した答えは : 0.332833になりました。 解析的な計算の結果は 1/3 なので、誤差は -0.000500 になり、ほぼ答えに近い事がわかります $
Download : sample-003.c ( SJIS 版 )
/* * 2015/06/26 sample-003.c */ /* * モンテ=カルロ法で、円周率の計算 * * 利用方法 * コンパイル * cc -Ic:\usr\c\include -o BASENAME.exe sample-003.c * 実行 * BASENAME * */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * */ #define FRACTION 1000 /* 試行回数 */ /* * 単位円 : x^2+y^2=1 の内部かどうか */ double f(double x, double y) { return x * x + y * y; } /* * randN */ int randR() { return (double)(rand() % FRACTION) / FRACTION; } /* monte_sum ( int i, int n, double min, double max ) S_i 〜 S_{n^1} の和を計算する */ double monte_sum ( int i, int n ) { if ( i < n ) { /* まだ計算が必要 */ return monte_sum ( i + 1, n ) + f(min+i*(max-min)/n)*(max-min)/n; } else { /* もう、全て計算した */ return 0.0; /* 残る結果は 0 になる */ } } /* * モンテ=カルロ法で円周率を計算 * * solve_monte () * */ double solve_reman () { return 4.0 * monte_sum ( 0, FRACTIONAL ) / FRACTIONAL; } /* * main 関数 */ int main ( void ) { s_print_string ( "関数 f(x)=x^2 を区間[0,1]で数値定積分する。\n" ); s_print_string ( "リーマンの定義に従って計算した答えは : " ); s_print_double ( solve_reman ( 0.0, 1.0 ) ); s_print_string ( "になりました。\n" ); s_print_string ( "解析的な計算の結果は 1/3 なので、誤差は " ); s_print_double ( solve_reman ( 0.0, 1.0 ) - 1.0/3.0 ); s_print_string ( " になり、ほぼ答えに近い事がわかります\n" ); return 0; }
$ ./sample-003.exe 関数 f(x)=x^2 を区間[0,1]で数値定積分する。 リーマンの定義に従って計算した答えは : 0.332833になりました。 解析的な計算の結果は 1/3 なので、誤差は -0.000500 になり、ほぼ答えに近い事がわかります $
#include <stdio.h> /* hello : "Hello, World" という文字列を返す */ char *hello(void) { return "Hello, World"; } /* print_s : 引数で渡された文字列を表示して改行 */ void print_s ( char *string ) { printf ( "*" ); printf ( string ); /* 引数の表示 */ printf ( "\n" ); /* 改行 */ } /* * main */ int main(void) { print_s ( "abc" ); /* "*" + "abc" + "\n" */ print_s ( hello() ); /* hello() が "Hello, World" を返すので.. */ return 0; } /* print_s ( "abc" ); ↓ string = "abc" !! "abc" はデータ print_s ( string ); ↓ string = "abc" { printf ( "*" ); printf ( string ); /* 引数の表示 */ printf ( "\n" ); /* 改行 */ } ↓ { printf ( "*" ); printf ( "abc" ); /* 引数の表示 */ printf ( "\n" ); /* 改行 */ } ↓ "abc" "\n" が表示される --------------------------------------- <誤り> print_s ( hello() ); ↓ string = hello() print_s ( string ); ↓ string = hello() !! hello() 関数呼出しなので Code !! hello()そのものが渡るのではなく !! hello() の結果が渡る { printf ( "*" ); /* 改行 */ printf ( string ); /* 引数の表示 */ printf ( "\n" ); /* 改行 */ } ↓ { printf ( "*" ); /* 改行 */ printf ( hello() ); /* 引数の表示 */ printf ( "\n" ); /* 改行 */ } --------------------------------------- <正しい> print_s ( hello() ); ↓ hello() が呼び出されて、"Hello, World" が返る それが print_s の引数に置き換わる print_s ( "Hello, World" ); ↓ 後は "abc" と同様... */
#include <stdio.h> #include "s_input.h" #include "s_print.h" int main(void) { s_print_double ( 2.0 ); /* double の出力 */ s_print_string ( " を " ); s_print_double ( 3.0 ); s_print_string ( " で割ると " ); s_print_double ( 2.0/3.0 ); /* double の計算 */ s_print_string ( " になる。" ); return 0; }
#include <stdio.h> #include "s_input.h" #include "s_print.h" double sum ( int i, int n, double x ) { /* sum ( 0, N, X ) は X を N 回、加えたもの */ if ( i < n ) { return sum ( i + 1, n, x ) + x; } else { return 0.0; } } int main(void) { s_print_double ( sum ( 0, 100, 0.3 ) ); /* 30 になるはず */ s_print_string ( "\n" ); s_print_double ( sum ( 0, 3000, 1.0/3000.0 ) ); /* 1 になるはず */ s_print_string ( "\n" ); return 0; }
/* * f(x)=x^2 を、区間[0,1]で定積分する * * 区間 [0,1] を 1000 等分して、短冊を作り、その面積を * 全て加える */ #include <stdio.h> #include "s_print.h" /* f(x)=x^2 */ double f(double x) { return x*x; } /* sum ( int k, int n ) \sum_{i=k}^{n-1} S_i = S_k + \sum_{i=k+1}^{n-1} S_i = S_k + sum( k+1, n ) 再帰呼出し */ double sum ( int k, int n ) { if ( k < n ) { return f(0.0 + k * (1.0-0.0)/1000.0)*(1.0/1000.0) + sum(k+1,n); } else { /* k == n の時は、\sum_{i=n}^{n-1} .. → 0 */ return 0.0; } } int main(void) { s_print_string ( "x^2 を [0,1] 区間で積分すると " ); s_print_double ( sum ( 0, 1000 ) ); s_print_string ( " になります。\n" ); return 0; }
/* * f(x)=x^2 を、区間[0,1]で定積分する * * 区間 [0,1] を 10000 等分して、短冊を作り、その面積を * 全て加える */ #include <stdio.h> #include "s_print.h" /* f(x)=x^2 */ double f(double x) { return x*x; } /* sum ( int k, int n ) \sum_{i=k}^{n-1} S_i = S_k + \sum_{i=k+1}^{n-1} S_i = S_k + sum( k+1, n ) 再帰呼出し */ double sum ( int k, int n ) { if ( k < n ) { return f(0.0 + k * (1.0-0.0)/10000.0)*(1.0/10000.0) + sum(k+1,n); } else { /* k == n の時は、\sum_{i=n}^{n-1} .. → 0 */ return 0.0; } } int main(void) { s_print_string ( "x^2 を [0,1] 区間で積分すると " ); s_print_double ( sum ( 0, 10000 ) ); s_print_string ( " になります。\n" ); return 0; } /* f(x)=x^2 f(x)=sin(exp(x^2)) */
/* * f(x)=sin(exp(x^2)) を、区間[0,1]で定積分する * * 区間 [0,1] を 10000 等分して、短冊を作り、その面積を * 全て加える */ #include <stdio.h> #include <math.h> #include "s_print.h" /* f(x)=sin(exp(x^2)) */ double f(double x) { return sin(exp(x*x)); } /* sum ( int k, int n ) \sum_{i=k}^{n-1} S_i = S_k + \sum_{i=k+1}^{n-1} S_i = S_k + sum( k+1, n ) 再帰呼出し */ double sum ( int k, int n ) { if ( k < n ) { return f(0.0 + k * (1.0-0.0)/10000.0)*(1.0/10000.0) + sum(k+1,n); } else { /* k == n の時は、\sum_{i=n}^{n-1} .. → 0 */ return 0.0; } } int main(void) { s_print_string ( "sin(exp(x^2)) を [0,1] 区間で積分すると " ); s_print_double ( sum ( 0, 10000 ) ); s_print_string ( " になります。\n" ); return 0; } /* f(x)=x^2 f(x)= */
/* 関数 f(x) が与えられた時に、 f(x_0)=0 となる x_0 を[近似的に..]求める 例 f(x) = a x^2 + b x + c f(x) = 0 となるのは x = \frac{-b\pm\sqrt{b^2-4ac}}{2a} f(x) が 5 次以上の多項式だったら ? f(x) = \sin{x} - x の時は ? 求め方(条件) 1. 実数解しか考えない 2. f(x) は連続関数 3. 予め、区間[a,b]があたえられていて、 f(a)*f(b)<0 となっている。 -> 区間[a,b] 内に f(x_0)=0 となる x_0 (実数) が含まれる (中間値の定理) 方針 答えが [a,b] の中にあるので、 m=(a+b)/2 とし 答えが、[a,m] と [m,b] のどちらにあるかを調べる */ #include <stdio.h> #include "s_print.h" /* f(x) = x^2 - 2 x - 3 = (x-3)(x+1) -> x = -1, 3 */ double f( double x ) { return x * x - 2 * x - 3; } /* f(x_0) = 0 となる x_0 が区間 [a,b] の中にはいっていて a < b f(a) < 0 < f(b) */ double solve ( double a, double b ) { if ( b - a < 0.0001 ) { /* この位精度があれば解とみなそう.. */ return (b+a)/2.0; } else { /* まだ、[a,b] は広過ぎるので、幅を狭める必要がある */ if ( f((b+a)/2.0) > 0 ) { /* 答えは、[a,(b+a)/2.0] の中にある */ return solve ( a, (b+a)/2.0 ); } else { /* 答えは、[(b+a)/2.0,b] の中にある */ return solve ( (b+a)/2.0, b ); } } } int main(void) { s_print_string ( "f(x)=x^2-2x-3=0 の解は " ); s_print_double ( solve ( 0.0, 4.0 ) ); s_print_string ( "です。\n" ); return 0; }
/* 関数 f(x) が与えられた時に、 f(x_0)=0 となる x_0 を[近似的に..]求める 例 f(x) = a x^2 + b x + c f(x) = 0 となるのは x = \frac{-b\pm\sqrt{b^2-4ac}}{2a} f(x) が 5 次以上の多項式だったら ? f(x) = \sin{x} - x の時は ? 求め方(条件) 1. 実数解しか考えない 2. f(x) は連続関数 3. 予め、区間[a,b]があたえられていて、 f(a)*f(b)<0 となっている。 -> 区間[a,b] 内に f(x_0)=0 となる x_0 (実数) が含まれる (中間値の定理) 方針 答えが [a,b] の中にあるので、 m=(a+b)/2 とし 答えが、[a,m] と [m,b] のどちらにあるかを調べる */ #include <stdio.h> #include <math.h> #include "s_print.h" /* f(x) = x-sin(x)-0.5 */ double f( double x ) { return x-sin(x)-0.5; } /* f(x_0) = 0 となる x_0 が区間 [a,b] の中にはいっていて a < b f(a) < 0 < f(b) */ double solve ( double a, double b ) { if ( b - a < 0.00000001 ) { /* この位精度があれば解とみなそう.. */ return (b+a)/2.0; } else { /* まだ、[a,b] は広過ぎるので、幅を狭める必要がある */ if ( f((b+a)/2.0) > 0 ) { /* 答えは、[a,(b+a)/2.0] の中にある */ return solve ( a, (b+a)/2.0 ); } else { /* 答えは、[(b+a)/2.0,b] の中にある */ return solve ( (b+a)/2.0, b ); } } } int main(void) { s_print_string ( "f(0.0)=" ); s_print_double ( f(0.0) ); s_print_string ( "\n" ); s_print_string ( "f(PI/2.0)=" ); s_print_double ( f(M_PI/2.0) ); s_print_string ( "\n" ); s_print_string ( "f(x)=x-sin(x)-0.5=0 の解は " ); s_print_double ( solve ( 0.0, M_PI/2.0 ) ); s_print_string ( "です。\n" ); s_print_string ( "f(solve()) の値は " ); s_print_double ( f(solve ( 0.0, M_PI/2.0 )) ); s_print_string ( "です。\n" ); return 0; }
/* * CDATE FILENAME * * 二つの整数値をキーボードから入力し、その四則並びに余りを出力する */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * print_int_result */ void print_int_result ( char *name, int a, int b, char op, int value ) { /* print_int_result ( "和", 5, 10, '+', 15 ); -> 和 : 5 + 10 = 15 */ s_print_string ( name ); /* 何を計算したか (和) */ s_print_string ( " : " ); s_print_int ( a ); /* 一つ目の数(5) */ s_print_char ( op ); /* 演算子(+) */ s_print_int ( b ); /* 二つ目の数(10) */ s_print_char ( '=' ); /* 等号(=) */ s_print_int ( value ); /* 結果(15) */ s_print_newline(); } /* * print_int_calc */ void print_int_calc ( int a, int b ) { print_int_result ( "和", a, b, '+', a + b ); /*和*/ print_int_result ( "差", a, b, '-', a - b ); /*差*/ print_int_result ( "積", a, b, '*', a * b ); /*積*/ /* ここは自分で */ /*商*/ print_int_result ( "余り", a, b, '%', a % b ); /*余り*/ } /* * print_int_calc_1 */ void print_int_calc_1 ( int a ) { print_int_calc ( a, s_input_int() ); } /* * main */ int main( int argc, char *argv[] ) { print_int_calc_1 ( s_input_int() ); /* print_int_calc ( s_input_int(), s_input_int() ); の時、キーボードから 5, 10 といれる 後ろが 5, 前が 10 になります print_int_calc ( 10, 5 ) となる */ return 0; }
/* * CDATE FILENAME * * 二つの浮動小数点数値をキーボードから入力し、その四則を出力する * 課題 01 の int を double 変更するだけ */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * print_double_result */ void print_double_result ( char *name, double a, double b, char op, double \ value ) { s_print_string ( name ); s_print_string ( " : " ); s_print_double ( a ); s_print_char ( op ); /* 演算子(+) */ s_print_double ( b ); /* 二つ目の数(10) */ s_print_char ( '=' ); s_print_double ( value ); s_print_newline(); } /* * print_double_calc */ void print_double_calc ( double a, double b ) { print_double_result ( "和", a, b, '+', a + b ); print_double_result ( "差", a, b, '-', a - b ); print_double_result ( "積", a, b, '*', a * b );/*積*/ /* ここは自分で */ /*商*/ /* 浮動小数点数の場合は「余り」の演算はない */ /* 10.0 % 3.0 とは「エラー」になる */ } /* * print_double_calc_1 */ void print_double_calc_1 ( double a ) { print_double_calc ( a, s_input_double() ); } /* * main */ int main( double argc, char *argv[] ) { print_double_calc_1 ( s_input_double() ); return 0; }
C 言語で、浮動小数点数型 ( double ) が使えるようになった C 言語で、誤差付きだが、実数計算ができるようになった 解析系 ( 微分、積分、確率等.. ) の計算ができる 数値の計算しかできない 「積分」といっても、原始関数が得られるわけではけではない 常に、誤差を含む 計算機が、「有限」しか扱えないので 「数学」では、「無限」が扱える 「数学」では、「できる」か「できない」かの何方か 数学で「解ける」という場合は「等号が成立」する 問題が難しくなっている 「解けない」場合が増える 「計算機」は、「近付ける」事ができる(等しくはならない) 計算機で「解ける」は「近似値」が得られる 「答え」要求される条件が弱いので、 -> 数学で「解け」ない問題が「解ける」 数値積分 I = \int_a^b f(x) dx の近似値を求める リーマンの公式より \int_a^b f(x) dx = \lim_{n\rightarrow\infty} \frac{b-a}{n}\sum_{i=0}^{n-1} f(a+i\frac{b-a}{n}) なので、n を適当な大きな数 ( 1000 とか.. 必要なだけ大きく ) で、I の近似値とする。
課題プログラム内の「/*名前:ここ*/」の部分を書き換え「/*この部分を完成させなさい*/」の部分にプログラムを追加して、プログラムを完成させます。
Download : 20150626-01.c ( SJIS 版 )
/* * TITLE * * CDATE FILENAME * * 一つ浮動小数点数値をキーボードから入力し、その立方根を出力する * 手段としては、「二分法」を使う */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * */ #define EPSILON 0.00000001 /* 誤差幅 */ /* * double regula_falsi_cubic_root ( double a, double min, double mid, double \ max ) * double a 立方根の元になる数(正を仮定している) * double min, max 根の入る区間の範囲 * double mid min と mid の中点 * return a 立方根 * 二分法により、a の立方根を求める * 0 < min < a の立方根 < max */ double regula_falsi_cubic_root ( double a, double min, double mid, double \ max ) { if ( max - min < EPSILON ) { /* 十分に精度が上った */ return mid; /* 中点の値を答として返す */ } else { /* まだ、狭める必要がある */ /* min が解のどちら側にあるかを調べ.. それに併せて区間を調整 */ /* f(x)=x^3-a */ if ( mid * mid * mid - a < 0.0 ) { /* f(mid) の符号を確認 */ /* 解の左にあった */ /* ** この部分を完成させなさい */ } else { /* 解の右にあった */ return regula_falsi_cubic_root ( a, min, (min+mid)/2.0, mid ); } } } /* * 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 の立方根を計算し、負数を返す */ /* ** この部分を完成させなさい */ } else if ( a < 1.0 ) { /* a が 1.0 以下なら */ /* ** この部分を完成させなさい */ /* 立方根は a と 1.0 の間にある */ } else { /* そうでなければ.. */ return regula_falsi_cubic_root ( a, 1.0, (1.0+a)/2.0, a ); /* 立方根は 1.0 と a の間にある */ } } /* * void print_cubic_root ( double a ) * double a 立方根を計算する数 * 元の数と、立方根を出力する */ void print_cubic_root ( double a ) { s_print_double ( a ); s_print_string ( " の立方根は " ); /* ** この部分を完成させなさい */ s_print_string ( " です。\n" ); } /* * main */ int main( double argc, char *argv[] ) { s_print_string ( "実数値を一つ入力してください : " ); print_cubic_root ( s_input_double() ); return 0; }
12.34
$ ./20150626-01-QQQQ.exe 実数値を一つ入力してください : 12.340000 12.340000 の立方根は 2.310850 です。 $
Download : 20150626-02.c ( SJIS 版 )
/* * TITLE * * CDATE FILENAME * * 再帰曲線である C-Curve を描画するプログラムを作る * */ #include <stdio.h> /* * 亀プログラムには、s_turtle.h が必要 */ #include "s_turtle.h" /* * 再帰曲線 */ void move ( int n ) { if ( n <= 0 ) { } else { s_turtle_move(); move ( n - 1 ); } } void turn ( int n ) { if ( n <= 0 ) { } else { s_turtle_turn(); turn ( n - 1 ); } } void jump ( int n ) { if ( n <= 0 ) { } else { s_turtle_jump(); jump ( n - 1 ); } } /* * C カーブ * + + + + + + * + + + + * + + + + + ====> + + ====> + + * */ void ccurve ( int n ) { if ( n <= 0 ) { move ( 4 ); } else { turn ( 7 ); ccurve ( n - 1 ); /* ** この部分を完成させなさい */ turn ( 7 ); } } int main ( void ) { turn ( 2 ); jump ( 40 ); turn ( 4 ); ccurve ( 8 ); putchar ( '>' ); putchar ( getchar() ); return 0; }
abc123
$ ./20150626-02-QQQQ.exe $