/*
* 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 になり、答えに近い事が分ります $