Powered by SmartDoc

ソフトウェア概論A/B (2017/07/14)
Ver. 1.0

2017年7月14日
栗野 俊一
kurino@math.cst.nihon-u.ac.jp
http://edu-gw2.math.cst.nihon-u.ac.jp/~kurino/2017/soft/soft.html
ソフトウェア概論 A/B2017年7月14日 の資料

目次

講義資料

当日の OHP 資料

講義で利用するサンプルプログラム

Download : sample-001.c

sample-001.c
/*
 * 2017/07/14 sample-001.c
 */

/*
 * 二分法による代数方程式の解法
 *
 * 利用方法
 *		コンパイル
 *			cc -I ~/c/include -c sample-001.c
 *		リンク
 *			cc -o sample-001.exe sample-001.c -lm
 *		実行
 *			./sample-001.exe
 */

#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.c の実行結果
$ ./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

sample-002.c
/*
 * 2017/07/14 sample-002.c
 */

/*
 * リーマン積分
 *
 * 利用方法
 *		コンパイル
 *			cc -I ~/c/include -c sample-002.c
 *		リンク
 *			cc -o sample-002.exe sample-002.c 
 *		実行
 *			./sample-002.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) {

	   /*
		* 引数 x に対して、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 になる */
	}
}

/*
 * リーマン積分
 *
 *   関数の積分値が、小さな幅の短冊の面積の和で近似できる事を利用
 *
 *   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.c の実行結果
$ ./sample-002.exe
関数 f(x)=x^2 を区間[0,1]で数値定積分する。
リーマンの定義に従って計算した答えは : 0.332833になりました。
解析的な計算の結果は 1/3 なので、誤差は -0.000500 になり、ほぼ答えに近い事がわかります
$ 

本日の課題

課題 20170714-01 : 立方根を出力する

Download : 20170714-01.c

20170714-01.c
/*
 * 20160715-01-QQQQ.c
 *
 *	一つ浮動小数点数値をキーボードから入力し、その立方根を出力する
 *	手段としては、「二分法」を使う
 *
 *	コンパイル :
 *		cc -I ~/c/include -c 20160715-01-QQQQ.c
 *		cc -o 20160715-01-QQQQ.exe 20160715-01-QQQQ.o
 *	実行 :
 *		./20160715-01-QQQQ.exe
 *
 */

#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
20170714-01.c の実行結果
$ ./20170714-01-QQQQ.exe
実数値を一つ入力してください : 12.340000
12.340000 の立方根は 2.310850 です。
$ 

課題 20170714-02 : CSV ファイル内の総計を求める

Download : 20170714-02.c

20170714-02.c
/*
 * 20160715-02-QQQQ.c
 *
 *	CSV ファイル内の総計を求める
 *
 *	コンパイル :
 *		cc -I ~/c/include -c 20160715-02-QQQQ.c
 *		cc -o 20160715-02-QQQQ.exe 20160715-02-QQQQ.o
 *	実行 :
 *		./20160715-02-QQQQ.exe
 *
 */
/*
 *	第一引数で与えられた csv ファイル内の、
 *	A1:J10 (10x10) の行の和を J1:J10 に入れるた結果を
 *  第二引数で与えられた csv ファイルに保存する。
 *
 *	この課題を解く時には、
 *		PNAME/s_csv
 *  に、ファイル 20160715-02-QQQQ.c, 20160715-02.csv を保存し、
 *		make TEST=20160715-02-QQQQ
 *  で、コンパイル、リンク、実行を行う
 *		20160715-DIRout.csv
 *  が作られ、その内容が表示されれば OK
 */

#include <stdio.h>

#include "s_cellio.h"	/* CSV ファイルの操作に必要 */
#include "s_csvio.h"

/*
 * void sum_row ( int row, int sum, int col, int col_max )
 *		row		処理対象になる行番号
 *		sum		ここまでのセルの値の和
 *		col		処理対象になる列番号
 *		col_max	最大の列番号 - 1 であると同時に、総和の保存先の列番号
 */

void	sum_row ( int row, int sum, int col, int col_max )	{

	if ( col < col_max ) {	/* 列番号がまだ、最大値になっていない */
		sum_row ( row, sum + s_get_cell_with_position ( col, row ), col + 1, col_max );
							/* 注目している row, col の値を、*/
							/* sum に加えて、次の列(col+1)へ */
	} else {				/* 列が最大を越えたので.. */

	/*
	**	 この部分を完成させなさい
	*/

							/* その場所に、和を記録する */
	}

}

/*
 * void sum_all_row ( int row, int row_max )
 *		row		処理対象になる行番号
 *		row_max	最大の行番号 - 1
 */

void sum_all_row ( int row, int row_max ) {

	 if ( row < row_max ) {	/* 行番号がまだ、最大値になっていない */
		sum_row ( row, 0, 0, 10 );			/* その行の総和を計算する */

											/* 次の行 (row+1) を計算 */

	/*
	**	 この部分を完成させなさい
	*/

	} else {				/* 行が最大を越えたので.. */
		/* やる事は何もない */
	}
}

/*
 * update_csv
 */

void update_csv ( char *in_file, char *out_file ) {

	s_load_csv ( in_file );		/* 第一引数のファイルからデータを入手 */

	sum_all_row ( 0, 10 );		/* 0 列目から 10 列分の総和を計算 */

	/* 計算結果を第二引数のファイルに保存 */

	/*
	**	 この部分を完成させなさい
	*/

}

/*
 * main
 */

int main ( int argc, char *argv[] ) {

	if ( argc == 3 ) {	/* 引数の個数が 2 ( = 3-1 ) の時 */
		update_csv ( argv[1], argv[2] );
						/* 第一引数のファイルを変更して第二引数のファイルに */
	} else {
		printf ( "ファイル名を二つ指定してください\n" );
	}

  return 0;
}
入力ファイル例(20170714-02.csv)
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 
40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 
70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 
90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 
20170714-02.c の実行結果
$ ./20170714-02-QQQQ.exe 20170714-02.csv 20170714-02out.csv
0,	1,	2,	3,	4,	5,	6,	7,	8,	9,	45
10,	11,	12,	13,	14,	15,	16,	17,	18,	19,	145
20,	21,	22,	23,	24,	25,	26,	27,	28,	29,	245
30,	31,	32,	33,	34,	35,	36,	37,	38,	39,	345
40,	41,	42,	43,	44,	45,	46,	47,	48,	49,	445
50,	51,	52,	53,	54,	55,	56,	57,	58,	59,	545
60,	61,	62,	63,	64,	65,	66,	67,	68,	69,	645
70,	71,	72,	73,	74,	75,	76,	77,	78,	79,	745
80,	81,	82,	83,	84,	85,	86,	87,	88,	89,	845
90,	91,	92,	93,	94,	95,	96,	97,	98,	99,	945
$ 
出力ファイル例(20170714-02out.csv)
0,	1,	2,	3,	4,	5,	6,	7,	8,	9,	45
10,	11,	12,	13,	14,	15,	16,	17,	18,	19,	145
20,	21,	22,	23,	24,	25,	26,	27,	28,	29,	245
30,	31,	32,	33,	34,	35,	36,	37,	38,	39,	345
40,	41,	42,	43,	44,	45,	46,	47,	48,	49,	445
50,	51,	52,	53,	54,	55,	56,	57,	58,	59,	545
60,	61,	62,	63,	64,	65,	66,	67,	68,	69,	645
70,	71,	72,	73,	74,	75,	76,	77,	78,	79,	745
80,	81,	82,	83,	84,	85,	86,	87,	88,	89,	845
90,	91,	92,	93,	94,	95,	96,	97,	98,	99,	945

課題 20170714-03 : 関数 sin(x) の区間 [0,π/4] の定積分値

Download : 20170714-03.c

20170714-03.c
/*
 * 20170714-03-QQQQ.c
 *
 *	関数 sin(x) の区間 [0,π/4] の定積
 *
 *	コンパイル :
 *		cc -I ~/c/include -c 20170714-03-QQQQ.c
 *		cc -o 20170714-03-QQQQ.exe 20170714-03-QQQQ.o
 *	実行 :
 *		./20170714-03-QQQQ.exe
 *
 */

#include <stdio.h>
#include <math.h>		/* 数学的関数 sin を利用するので.. */

#include "s_input.h"
#include "s_print.h"

/*
 * リーマン積分を利用する
 */

#define FRACTIONAL 1000 /* 区間の等分数 */

/*
 * f(x)=sin(x)
 */

double f(double x) {

	   /*
		* 引数 x に対して、x の 正弦値 sin(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 )	{	/* まだ計算が必要 */
		/* 注目している短冊の面積と、残りの部分の面積の和を計算する */

	/*
	**	 この部分を完成させなさい
	*/

	} else {			/* もう、全て計算した */
		return 0.0;		/* 残る結果は 0 になる */
	}
}

/*
 * リーマン積分
 *
 *   関数の積分値が、小さな幅の短冊の面積の和で近似できる事を利用
 *
 *   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)=sin(x) を区間[0,π/4]で数値定積分する。\n" );

	s_print_string ( "リーマンの定義に従って計算した答えは : " );

	/*
	**	 この部分を完成させなさい
	*/

	s_print_string ( "になりました。\n" );

	s_print_string ( "解析的な計算の結果は 1-√2/2 なので、誤差は " );
	s_print_double ( solve_reman ( 0.0, M_PI/4.0 ) - (1.0-sqrt(2.0)/2.0) );
	s_print_string ( " になり、ほぼ答えに近い事がわかります\n" );

	return 0;
}
20170714-03.c の実行結果
$ ./20170714-03-QQQQ.exe
関数 f(x)=sin(x) を区間[0,π/4]で数値定積分する。
リーマンの定義に従って計算した答えは : 0.292616になりました。
解析的な計算の結果は 1-√2/2 なので、誤差は -0.000278 になり、ほぼ答えに近い事がわかります
$