Document

プログラミング入門
第14回講義
関数(その2)
関数の復習(2)
関数の例1 ニュートン法(4)
関数の例2 桁数(11)
関数の例3 ある桁の数(14)
エラーチェックと強制終了(17)
関数の例4 2進→10進変換(20)
マークのあるサンプルプログラムは
/home/course/prog0/public_html/2013/lec/source/
下に置いてありますから、各自自分のディレクトリに
コピーして、コンパイル・実行してみてください
Prog-0 2013 Lec14-1
Copyright (C) 1999 – 2013 by Programming-0 Group
#include <stdio.h>
double nijou(double);
main()
{
double a = 1.73 , b , c ;
b = nijou(a);
c = nijou(1.41);
...(以下略)
}
double 1 nijou( double x )
{
double y;
y = x * x;
return y ;
}
Prog-0 2013 Lec14-2
関数の復習
引数が仮引数にコピーされる
関数内で仮引数の値に従って
計算(処理)
戻り値が関数の値となる
mainの前にプロトタイプ宣言
mainの後ろに関数本体の宣言
Copyright (C) 1999 – 2013 by Programming-0 Group
関数の動作おさらい
#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 ;
}
Prog-0 2013 Lec14-3
1.
2.
3.
mainの最初の実行文から開始
nijou(3.0)が評価される
3.0が関数の仮引数xにコピーされ、関数
nijouに実行が移る
4. 計算の結果、yに9.0が入り、「return y」で
9.0がnijou関数の結果となる
5. nijou関数の結果がa1に代入される。
6. 次にnijou(x)が評価される
7. x(2.0)が関数のxにコピーされ、関数nijouに
実行が移る
8. 計算の結果yに4.0が入り、「return y」で4.0
がnijou関数の結果となる
9. nijou関数の結果がa2に代入される
10. a3も同様に計算される
11. printfでa1,a2,a3が表示される
12. 終了
(教科書P165参照)
Copyright (C) 1999 – 2013 by Programming-0 Group

関数の例1―ニュートン法(p.178)
非線型方程式 f(x)=0 の解を求める方法
例題として、ニュートン法で
x a 0
2
の解 x を求める
(ただし、 a は与えられた定数、例えば1,2など)


Prog-0 2013 Lec14-4
Copyright (C) 1999 – 2013 by Programming-0 Group
ニュートン法の原理
y  f (x) と x軸との交点を
f(x)
y
反復法によって求める

ステップ①: 初期値 x0 を与え、
点(x0,y0)における y=f(x) の接線
y  y0  f (x0 ) (x  x0 )
と x軸の交点x1を求める ただし y0=f(x0)
ステップ② :
点(x1,y1)における y=f(x) の接線

y  y1  f (x1) (x  x1)
と x軸との交点を求める ただし y1=f(x1)
以後xnにつきステップ①②を繰り返す

Prog-0 2013 Lec14-5

傾き f'(x0)
傾き f'(x1)
求める x
傾き f'(x2)
0
x
x3 x2
x1
x0
x0, x1, x2 , x3 …と だんだん
f(x) の零点(根になる点)に
近づいていく
Copyright (C) 1999 – 2013 by Programming-0 Group
ニュートン法の原理まとめ
初期値x0とし、そこでの接線とx軸
との交点を順次求めて行く
f (x k1)
x k  x k1 
(k  0,1,2
f (x k1)
f(x)
y
f(x0)
)
傾き f'(x1)
求める x
ここで、
d f ( x)
f ' ( x) 
 d x
とする。
傾き f'(x0)
f(xk)<(精度)
0
傾き f'(x2)
f(x1)
f(x2)
x
x3 x2
x1
x0
求めた f(xk) の値が一定の値(精度)
より小さければ終了
Prog-0 2013 Lec14-6
Copyright (C) 1999 – 2013 by Programming-0 Group
フローチャート
はじめ
関数
f(x,a)
読みこみ
a←定数
関数
df(x)
x←初期値
fx← x*x-a
f(x,a)>精度
真
x←
x-f(x,a)/df(x)
x出力
終り
Prog-0 2013 Lec14-7
dfx←2*x
dfxは、fxつまり x2
の1階微分だから
2x
偽
return fx
return dfx
初期値は一般的に解より少し大きい値aとする。
精度はマクロEPSで与え、値は10-6とする
また、一般的には「f(x,a)<精度」に関して、本当は
f(x,a)の絶対値を取る必要があるが、初期値を解より
大きいaとしたので、負になることは考えない
Copyright (C) 1999 – 2013 by Programming-0 Group
#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;
double f(double x, double a)
{
double fx;
fx = x*x - a;
return fx;
}
printf("x(k-1)\t\tfx\t\tdfx\t\tx(k)
\t\tf(x,%f)", a);
while((fx = f(x,a)) > EPS ){
double df(double x)
dfx = df(x);
代入してから比較
{
printf("%f\t%f\t%f",x,fx,dfx);
double dfx;
x = x - fx/dfx;
dfx = 2.0*x;
printf("\t%f\t%12.10f\n",x,f(x,a));
return dfx;
}
}
printf("sqrt(%f) : %12.10f\n",a,x);
/home/course/prog0/public_html/2013/lec/source/lec14-1.c
}
Prog-0 2013 Lec14-8
Copyright (C) 1999 – 2013 by Programming-0 Group
実行結果
xk-1
xkでの関数f(xk)の値。この
値が0になった時のxkが求
める値であるが、 f(xk)は完
全に0にはならないので、非
常に小さい値EPS以下に
なった時のxkを解とする
std0dc0{s1000000}1: ./a.out
xkつまり
xk-1 - fx/dfx
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:
Prog-0 2013 Lec14-9
値がEPS (精度)以下になっ
た時点でループを終了する
Copyright (C) 1999 – 2013 by Programming-0 Group
他の方程式( x  a  0 )を解く場合
3
double f(double x, double a)
{
double fx;
fx = x*x*x – a 2 ;
return fx;
}

double df(double x)
{
dfxは、fxつまり x3
の1階微分だから
double dfx;
3x2
dfx = 3.0*x*x;
return dfx;
}
main関数での変更は最小
限で済む
簡単な変更で、もっと複雑な
方程式の解を求めることも
可能
/home/course/prog0/public_html/2013/lec/source/lec14-2.c
Prog-0 2013 Lec14-10
Copyright (C) 1999 – 2013 by Programming-0 Group
関数の例2:
桁数を知る(1)
入力された数の桁数を返す関数を作る
(例えば、「12345678」は8桁)
受け渡しの要件:1入力・1出力
入力:数 ⇨ int型
出力:桁数
⇨ int型
関数名は桁数(digits)からdigitsとする。
digitsのプロトタイプ宣言は以下のようになる。
int digits(int);
Prog-0 2013 Lec14-11
Copyright (C) 1999 – 2013 by Programming-0 Group
桁数を知る(2)
10で割った答えが0でなければ、2桁以上の数
100(=1010)で割った答えが0でなければ、3桁以上の数
1000(=10010)で割った答えが0でなければ、4桁以上の数
…
と割る数を順次10倍し、答えが0でない限り計算を続ける。
答えが初めて0になったときを見つける。
ループ回数
数が一桁の場合は
一度もループに入らない
int digits(int x)
初期値として 桁数=1,y=10とする
{
int keta = 1 , y = 10;
1
2
3
4
5
6
7
右表はこの時点の値
8
while((x / y) > 0){
y *= 10;
xをyで割った答えが0以外の間以下の処理
keta++;
yを10倍する(つまり今度は一桁上を見る)
}
桁数に1加える
return keta;
}
Prog-0 2013 Lec14-12
ループここまで
関数の値として桁数をリターン
y
x/y
10 1234567
100 123456
1000 12345
10000 1234
100000 123
1000000 12
keta
1
2
3
4
5
6
10000000 1
7
100000000 0
8
引数に12345678が渡された
時のループの様子
整数どうしの割り算は小数点
以下は切り捨てられる
Copyright (C) 1999 – 2013 by Programming-0 Group
#include<stdio.h>
プログラムと実行結果
int digits(int);
main()
{
int i, j;
scanf("%d",&i);
j = digits(i);
printf("%d の桁数は %d です\n",i,j);
実行結果
std1dc1{s1000000}1: ./a.out
12345678
12345678 の桁数は 8 です
std1dc1{s1000000}2:
}
int digits(int x)
{
int keta = 1 3 , y = 10
4
;
while((x / y) > 0){
y *= 10;
keta++;
}
return keta;
}
Prog-0 2013 Lec14-13
/home/course/prog0/public_html/2013/lec/source/lec14-3.c
Copyright (C) 1999 – 2013 by Programming-0 Group
関数の例3:
ある桁の数を知る(1)
入力された数字の指定された桁の数を返す関数を作る
(例えば、654321の下から2桁目は2)
受け渡しの要件:2入力・1出力
二つの入力:
データ ⇨ int型
桁数 ⇨ int型
結果(その桁の数) ⇨ int型
関数名はget_1_digitとした。この関数のプロトタイプ宣
言は以下のようになる。 データ
桁数
int get_1_digit(int,int);
Prog-0 2013 Lec14-14
Copyright (C) 1999 – 2013 by Programming-0 Group
ある桁の数を知る(2)
例えば654321の(下から)2桁目は2
654321 % 100 = 21
剰余算
21 / 10 = 2
つまり
求める数 = (データ % 10桁数)/ 10(桁数ー1)
5
10(桁数ー1) をどう作るか?
⇨ 10を(桁数ー1)回掛け合わせる(ループにて)
Prog-0 2013 Lec14-15
Copyright (C) 1999 – 2013 by Programming-0 Group
#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);
実行結果
std1dc1{s1000000}1: ./a.out
654321
654321 の 2 桁目は 2 です
std1dc1{s1000000}2:
}
int get_1_digit(int x, int pos)
{
int i, j, k = 1;
10(桁数ー1)のためのループ
for(i = 1 ; i < pos ; i++){
k *= 10;
}
j = x % (k * 10) / k;
return j;
(データ%10桁数)/10(桁数ー1)
の計算
}
/home/course/prog0/public_html/2013/lec/source/lec14-4.c
Prog-0 2013 Lec14-16
Copyright (C) 1999 – 2013 by Programming-0 Group
例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年(一秒ごとにひとつカウントした場合)
Prog-0 2013 Lec14-17
Copyright (C) 1999 – 2013 by Programming-0 Group
関数の例4:
2進数から10進数への変換(1)
入力された数字を2進数とみなして10進数に変換するプロ
グラム(これまで作った関数を使う)
構成
main
digits (桁数を求める)
get_1_digit (数のうち一桁だけを取り出す)
main()で行うこと:
0が入力されるまで無限ループで数を読み込む
2進数から10進数への変換(次ページ)
Prog-0 2013 Lec14-18
Copyright (C) 1999 – 2013 by Programming-0 Group
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;
}
Prog-0 2013 Lec14-19
計算順序
ループの様子
n
exp
total
1
1
1
1
2
3
0
4
3
1
8
11
0
16
11
1
32
43
Copyright (C) 1999 – 2013 by Programming-0 Group
#include<stdio.h>
#include<stdlib.h>
int digits(int);
int get_1_digit(int, int);
⇨左から続く
main()
int digits(int x)
{
{
int data, keta, i, n, exp, total;
int keta = 1 , k = 10;
while(1){
/* データ読み込みとチェック */
while((x / k) > 0){
printf("8桁以下の2進数を入力 ==> ");
k *= 10;
scanf("%d",&data);
keta++;
if (data == 0) exit(0);
}
if (data > 11111111 || data < 0){
return keta;
printf("変換出来る範囲を越えています\n");
}
continue;
8
}
int get_1_digit(int x, int pos)
/* 桁数計算 */
{
keta = digits(data);
int i, j, k = 1;
/* 10進数に変換 */
total = 0;
for(i = 1 ; i < pos ; i++){
exp = 1;
k *= 10;
for(i = 1 ; i <= keta ; i++){
}
n = get_1_digit(data, i);
j = x % (k * 10) / k;
if((n != 0) && (n != 1)){ /*データチェック*/
return j;
printf("データが0か1ではありません\n");
}
exit(8);
}
total += n * exp;
exp *= 2;
lec14-6a.cが記載されたプログラム。
}
後は様々なバリエーション
printf("2進 : %d -> 10進 : %d\n",data,total);
}
}
右に続く⇨
/home/course/prog0/public_html/2013/lec/source/lec14-6{a,b,c,d}.c
2進数→10進数変換プログラム
Prog-0 2013 Lec14-20
Copyright (C) 1999 – 2013 by Programming-0 Group
実行例
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
9
8
std1dc1{s1000000}3:
Prog-0 2013 Lec14-21
Copyright (C) 1999 – 2013 by Programming-0 Group
エラーチェック
エラーチェックとは 予想される間違いを検出 6 すること
前述の例4のプログラムの場合、以下のような入力誤りが予想される
負の数が入力される(例:-101)
1桁の数が入力される(例:8)
エラーを検出した場合はエラーに応じた処理を行う(「エラー処理」と言う)
エラーの重大さによって、処理が異なることがある(例えば軽度なエラーは処理を
続行させ、重度なエラーは処理を中止するなど)
例4のプログラムの場合
負の数だった場合:「変換出来る範囲を越えています」と表示して再度データ入力から
やり直す
1桁の数だった場合、「データが適切ではありません」と表示してプログラムを強制終了
させる。(強制終了は次ページ参照)
エラーチェックをしっかりしておくとプログラムの誤動作を未然に防ぐことが出来る
7
Prog-0 2013 Lec14-22
Copyright (C) 1999 – 2013 by Programming-0 Group
プログラムの強制終了
プログラムの強制終了の方法
<stdlib.h> をインクルードする。
exit(整数); で どこからでもプログラムを強制終了させることが出来る。
プログラム実行後、この引数はシェル変数に渡される(例えば tcsh だと
「$?」、 csh だと 「$status」 というシェル変数に格納される)
シェル変数の制約により渡せる引数は0~255までの整数のみに限られる。
このようにプログラムからシェル変数に値が渡る事で、
値によってエラーの理由を知ることが出来る
シェルスクリプトを使用して値によって動作を変える事が可能
例えば以下のような非常に簡単なプログラム (exit(8);で強制終了)の
実行終了後$statusを見ると8という数字が入っていることが分かる。
#include <stdio.h>
#include <stdlib.h>
main()
{
exit(8);
}
Prog-0 2013 Lec14-23
実行結果(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
Copyright (C) 1999 – 2013 by Programming-0 Group
補足: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");
}
Prog-0 2013 Lec14-24
#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
Copyright (C) 1999 – 2013 by Programming-0 Group