2011年2月28日月曜日

[C/C++] ニュートン法

ニュートン法は方程式
f(x) = 0        (1)
の解を求めるための方法です.原理を以下に説明します.

関数f(x)は3回微分可能だとします.方程式
f(x)= 0
の解をαとして,αの近くにある点x0の周りでテイラー展開すると
  (2)
となります.これよりΔx = α-x0が十分に小さければ
なので,
f(x0) + f'(x0)Δx = 0
は,式(2)の近似式と考えることができます.式(3)より,Δxの近似としてΔx0をΔx0=-(f(x0) / f'(x0))と選ぶと,x1 = x0 + Δx0 ≒ x0 + Δx = αは,x0よりアルファに対する良い近似となっているはずです.これを繰り返して得られる次の反復式が,式(1)に対するニュートン法です.

ニュートン法はΔ = α - x0が十分に小さい,つまり,初期値x0がαの近くにあるということが前提となっているので,初期値の選び方によっては収束しない可能性があります.したがって,あらかじめ反復回数の上限を設定しておかなければ,プログラムが終了しない可能性があります.

以上より,ニュートン法のアルゴリズムは以下のようになります.
  1. 初期値x0,小さい数 ε > 0,最大反復回数kmaxを与える.
  2. n = 0, 1, 2, …kmaxに対してxn+1 = xn - (f(xn) / f'(xn))を計算する.ここで,f'(x)はxの導関数.
  3. 補正量 - (f(xn) / f'(xn))の絶対値がεより小さくなれば xn + 1を解とし,そうでなければ解は見つからなかったこととする.また,反復回数がkmaxに達した場合も解が見つからなかったこととする.
このアルゴリズムをC/C++で書くと以下のようになります.

  x,ε,kmaxの入力
  k <- 0
  do
  {
          d <- -f(x)/f'(x)
          x <- x+d
          k <- k+1
  }while (|d| > ε and k < kmax)
  if
          k = kmax
          解が見つからない
  else
          xを出力
  endif

上記のアルゴリズムに基づいて,x - cosx = 0を解くためのプログラム例を以下に示します.

#include <stdio.h>
#include <math.h>

#define EPS pow(10.0, -8) //Epsilon setting
#define KMAX 10           //Maximum iteration function

double f(double x) ;      //Calculation of f(x)
double df(double x) ;     //Calculation of f'(x)

int main(void)
{
  int k = 0 ;
  double x, d ;
  printf("Input the initial value. \n") ;
  scanf("%lf", &x) ;
  do
  {
    d = -f(x)/df(x) ;
    x = x + d ;
    k ++ ;
  } while(fabs(d) > EPS && k < KMAX) ;

  if ( k == KMAX )
  {
    printf("An answer was not found \n") ;
  }
  else
  {
    printf("The answer is x = %f. \n", x) ;
  }

  return 0 ;
}

double f(double x)
{
  return(x - cos(x)) ;
}

double df(double x)
{
  return(1.0 + sin(x)) ;

}

上記のプログラムをコンパイル,実行すると以下のようになります(プログラムを3回実行した結果).
Input the initial value. 
1
The answer is x = 0.739085. 
Input the initial value. 
3
The answer is x = 0.739085. 
Input the initial value. 
5
An answer was not found 

2011年2月21日月曜日

[C/C++] エラーのチェック

fscanf関数は,ファイルのエラーや終わりを検出するとEOFをその戻り値として返します.しかし,EOFが返されたとしても,それだけではファイルの終端なのか,エラーが発生したのか分かりません.これを調べる関数としてfeof関数とferror関数があります.
  • feof関数:ファイルの終わりを検出すると0でない数を返し,そうでなければ0を返します.
  • ferror関数:ファイルにエラーがあると0でない数を返し,エラーがなければ0を返します.
データを読み込む際にエラーの発生を調べたい場合は,以下のようにします.

#include <stdlib.h>    //It is necessary to use "foes" and "ferror".
...
  if(foes(fp)) //If "foes" is not 0, it reaches the end of the file.
  {
    printf("It is the end of the file. \n") ;
  }
  if (ferror(fp))
  {
    printf("A reading error occurred. \n") ;

  }

データの書き込み時にも同様のチェックは必要となりますが,これにはfeof関数やferror関数を使う必要はありません.例えば,fprintf関数を使った場合は,fprintf関数が負の値を出力したかどうかを調べればよく,fwrite関数を使った場合はfwrite関数が出力した値とプログラム中で指定した要素数が等しいか否かを調べれば良いからです.

2011年2月20日日曜日

[C/C++] fopen関数のモード

fopen関数のモードとしては,以下の表のようなものがあります.

モードファイル機能ファイルが
ないとき
"r"テキスト読み取りエラー
"w"テキスト書き込み新規作成
"a"テキスト追加書き込み新規作成
"rb"バイナリ読み取りエラー
"wb"バイナリ書き込み新規作成
"ab"バイナリ追加書き込み新規作成
"r+"テキスト更新(読み取り及び書き込み)エラー
"w+"テキスト更新(読み取り及び書き込み)新規作成
"a+"テキスト更新(追加書き込み)新規作成
"r+b" or "rb+"バイナリ更新(読み取り及び書き込み)エラー
"w+b" or "wb+"バイナリ更新(読み取り及び書き込み)新規作成
"a+b" or "ab+"バイナリ更新(追加書き込み)新規作成

ファイル"test1.dat","test2.dat","test3.dat"の内容が,以下のように同じものだとします.
9876 ABCDEFGHIJK

その上で,以下のプログラムを実行します.

#include <stdio.h>

int main(void)
{
  FILE *frPlus, *fwPlus, *faPlus ;

  frPlus = fopen("test1.dat", "r+") ;
  fwPlus = fopen("test2.dat", "w+") ;
  faPlus = fopen("test3.dat", "a+") ;

  fprintf(frPlus, "%d", 1234) ;
  fprintf(fwPlus, "%d", 1234) ;
  fprintf(faPlus, "%d", 1234) ;

  fclose(frPlus) ; fclose(fwPlus) ; fclose(faPlus) ;

  return 0 ;

}

すると,以下のような結果が得られます.

test1.dat("r+"モード)
1234 ABCDEFGHIJK

test2.dat("w+"モード)
1234

test3.dat("a+"モード)
9876 ABCDEFGHIJK1234

この結果より,

  • "r+"モード ファイルを読み込んだ上で,データを先頭から上書きされる(読み込みも可能)
  • "w+"モード データを先頭から上書きし,読み込みも可能
  • "a+"モード データを最後尾に追記し,読み込みも可能

ということがわかります.

2011年2月19日土曜日

[C/C++] バイナリファイルの扱い

テキストファイルとは,文字や数字など人が見て分かる形(内容の意味は問わない)で記録したファイルのことで,バイナリファイルは,コンピュータの内部形式でプログラムやデータを記録したものです.バイナリファイルは人が見ても,何が書かれているのか分かりません.また,バイナリファイルの構造はコンピュータのアーキテクチャやOSによっても異なります.

<バイナリファイルの中身例>
<-Š )ÈŸT&' F¸S,K–{,KhÛeU¥´' òÅÄl§ÊŸj¡Ð± Ë
øyüÓŒ PK±J—Ç=ˆ2«z¢½‚} p –¡ù|ó¾ßhOyq9ÿNÓH³lç(¿p‘qÙE [« Âña
áüF$b*„0Ð ú4ÜÒS2Tƒ8 È 6in¢ \ô D4úk8aó 9?ò Z>ÙQ‹
÷‚ª@KG|z¿Ë,u²¤ôO ËhFýÅï #j¯\_Oa*æ¡t8 1±¢ 0 D4 Š.Ê6ìi > µ&”6 Qy8 P¹$øÙ6 ê) _pòŸ8 ´å :_“<ç÷PoÉ—Ã, $"]ÐÌnlM£H^–DÙr & êí’ M¬ArÕ-—5o BX­þÎZn>²K•Î #7ªÚ> !Ý “;©xÈm†.T}‘{-U» â Õ^ òr 4Ït úaO ãi)T†ºf|)É_ ½Â X› :+ÌgI³@=WÇ’ #]@R_«"F ÿ – JÓ+ E,vA–‡T5

テキストファイルとコンピュータで扱う際には,それらをコンピュータの内部形式(バイナリファイル)に変換する必要があります.そのためには,最初からバイナリファイルで扱った方が,
  1. ファイルのサイズを小さくできる
  2. 入出力を高速に実行できる
というメリットがあります.

以下に,int型の乱数を100個発生させたうえで,それらをテキストファイルとしてtext.datへ,バイナリファイルとしてbinary.datに出力するプログラム例を示します.ここで,整数型の段数を発生させるためにtime関数,rand関数,srand関数を利用しています.また,バイナリファイルとして出力するためにfwrite関数を使っています.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
  int i ;
  int a[100] ;
  FILE *fp ;

  if ((fp = fopen("text.dat", "w")) == NULL)
  {
    fprintf(stderr, "Unable to create file text.dat. \n") ;
    exit(1) ;
  }

  srand((unsigned)time(NULL)) ;
  for(i = 0 ; i < 100 ; i++)
  {
    a[i] = rand() ;
    fprintf(fp, "%d\n", a[i]) ;
  }

  fclose(fp) ;

  if((fp = fopen("binary.dat", "wb")) == NULL)
  {
    fprintf(stderr, "Unable to create file 'binary.dat'. \n") ;
    exit(1) ;
  }

  fwrite(a, sizeof(int), 100, fp) ;
  fclose(fp) ;

  return 0;

}

テキストファイルとして出力する際には,100回のループを行っているのに対して,バイナリファイルではfwrite関数を1回実行するだけで良い点に注意が必要です.

fwrite関数は上記のプログラムのように
  fwrite(変数の先頭アドレス, 各要素サイズ要素数, ファイルポインタ) ;
として使用します.プログラム中の
  fwrite(a, sizeof(int), 100, fp) ;
は,aというポインタで示される(配列はポインタに読み替えられる)アドレスから1つの要素サイズがsizeof(int)の100個のデータをファイルポインタfpに対応するファイルに書き込むという意味です.

システムにもよりますが,バイナリファイルの方がファイルサイズが半分以下となることもあります.なお,fwrite関数は書き込んだデータの数を返します.したがって,これが要素数に等しくない場合にはエラーが生じていることになります(このことを用いて,ファイルエラーが発生したかどうかを調べることができます).

2011年2月18日金曜日

[C/C++] ファイルと関数

関数間でファイルの受け渡しを行うにはポインタ変数と同じ考え方を用います.
    FILE *fp
とした場合は,アドレスfp(つまり,fpが存在する場所)を関数に渡すことになります.以下の例ではmain関数で読み込みファイル(input6.dat)および書き込みファイル(output.dat)をオープンし,それらが指すアドレスfin, foutを関数 file_read_write に渡すプログラム例を示します.また,file_read_writeはfp1が指すファイル(実体は input6.dat)の中身を読み込み,fp2が指すファイル(実体はoutput6.dat)へ書き込む関数です.
input6.datの中身
1.0 2.0 3.0 4.0 5.0

#include <stdio.h>
#include <stdlib.h>

void file_read_write(FILE *fp1, FILE *fp2) ;

int main(void)
{
  FILE *fin, *fout ;

  if ((fin = fopen("input6.dat", "r")) == NULL)
  {
    printf("File not found. : input6.dat \n") ;
    exit(1) ;
  }
  if ((fout = fopen("output6.dat", "w")) == NULL)
  {
    printf("File can not be created. output6.dat \n") ;
    exit(1) ;
  }
  file_read_write(fin, fout) ;

  fclose(fin) ;
  fclose(fout) ;

  return 0 ;
}

void file_read_write(FILE *fp1, FILE *fp2)
{
  double a ;
  while(fscanf(fp1, "%lf", &a) != EOF)
  {
    fprintf(fp2, "%f \n", a) ;
  }

}

上記のプログラムをコンパイル実行すると,output6.datが作成され,その中身は以下のようになります.
1.000000
2.000000
3.000000
4.000000
5.000000

2011年2月17日木曜日

[C/C++] ファイルへの出力

ファイルへの出力を行うには出力モードでファイルをオープンします.具体的には
    fp = fopen("ファイル名", "w")
とします."w"が書き込み(Write)モードであることを意味します.なお,書き込みの際にはファイルの先頭からデータを書き込みます."w"を指定してfopenを使った場合,ファイルが存在すると,そのファイルの内容が上書きされるので注意が必要です.また,既存のファイルに追加して書き込む場合は,追加(Append)モードを利用して
    fp = fopen("ファイル名", "a")
とします.

実際にファイルに書き込まれるのはファイルをクローズしたとき,つまり,fclose関数を使用したときなので,必ずfclose関数でファイルをクローズする必要があります.また,書き込みモード,追加モード共にファイルが存在しなければ新規にファイルが作成されますが,ディスク容量の制限やディレクトリにアクセス制限がある場合も考慮して,通常は,以下のようにファイルが作成できないばあいを考慮したプログラムとします.fopen関数は読み込みモードの時と同様に,ファイルが作成できなければ戻り値としてNULLを返します.

ここでは,ファイルの出力を行うための関数としてはfprint関数を使います.使い方はファイルポインタを指定する以外はprintf文と全く同じです.fprint文は以下のようにして用います.例ではファイルポインタfpに対応するファイルにdouble型の変数aの内容を出力しています.printf文と同様,変数仕様を変更すれば他の型の変数内容もファイルに出力することが可能です.

以下に,ファイル(input5.dat)からデータを読み込んで,その内容をファイル(output.dat)に出力し,さらにファイル(output5.dat)に追加出力するプログラム例を示します.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  FILE *fin, *fout ;
  double a ;

  if ((fin = fopen("input5.dat", "r")) == NULL)
  {
    printf("File not found. : input5.dat \n") ;
    exit(1) ;
  }
  if((fout = fopen("output5.dat", "w")) == NULL)
  {
    printf("File can not be created. : output5.dat \n") ;
    exit(1) ;
  }

  while(fscanf(fin, "%lf", &a) != EOF)
  {
    fprintf(fout, "Input data 1st time ---> %f \n", a) ;
  }
  fclose(fout) ;
  fclose(fin) ;

  fin = fopen("input5.dat", "r") ;
  fout = fopen("output5.dat", "a") ;
  while(fscanf(fin, "%lf", &a) != EOF)
  {
    fprintf(fout, "Input data 2nd time ---> %f \n", a) ;
  }
  fclose(fout) ;
  fclose(fin) ;

  return 0 ;

}

上記のプログラムをコンパイル実行すると,outpu5.datが作成され,その中身は以下のようになります.
Input data 1st time ---> 11.100000
Input data 1st time ---> 12.200000
Input data 1st time ---> 13.300000
Input data 1st time ---> 14.400000
Input data 1st time ---> 15.500000
Input data 1st time ---> 16.600000
Input data 1st time ---> 17.700000
Input data 2nd time ---> 11.100000
Input data 2nd time ---> 12.200000
Input data 2nd time ---> 13.300000
Input data 2nd time ---> 14.400000
Input data 2nd time ---> 15.500000
Input data 2nd time ---> 16.600000
Input data 2nd time ---> 17.700000

C/C++では標準入力,標準出力,標準エラー出力に対応するファイルポインタが,stdin, stdout, stderrとして定義されています.標準入力はキーボードに,標準出力および標準エラー出力はディスプレイ画面に割り当てられています.例えば,fprintf文でエラーを出力したい場合は以下のようにします.
    fprintf(stderr, "File can not be output \n") ;

2011年2月16日水曜日

[C/C++] 複数のファイルからの入力

複数のファイルからデータを読み込む際には,必要な数だけファイルポインタ変数を宣言します.例えば,2つのファイル(input1.dat,input2.dat)からデータを読み込む場合には,

  FILE *fp1, *fp2 ;

  if (( fp1 = fopen( "input1.dat""r")) == NULL )
  {
    printf("File not found. : input1.dat \n") ;
    exit(1) ;
  }
  if (( fp2 = fopen( "input2.dat""r")) == NULL )
  {
    printf("File not found. : input2.dat \n") ;
    exit(1) ;
  }

とします.

2011年2月15日火曜日

[C/C++] 入力データの終了判定

fscanf関数は,ファイルからデータを全て読み終わった時に"EOF (End of File)"を返します.これを使うと,データの個数があらかじめわかっていないときでもデータがなくなるまで全てのデータを読み込むことができます.具体的には,fscanf関数の値がEOFに一致するまでデータを読み込めば良いことになります.

ファイルからデータがなくなるまで読み込んで,その合計を求めるプログラム例を以下に示します.

入力のためのファイル(input4.dat)は以下のように設定します.
1.0     0.5      0.25      0.125      0.0625
0.03125 0.015625 0.0078125 0.00390625 0.001953125

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  double a, sum = 0.0 ;
  FILE *fp ;

  if (( fp = fopen( "input4.dat", "r")) == NULL )
  {
    printf("File not found. : input4.dat \n") ;
    exit(1) ;
  }
  while( fscanf(fp, "%lf", &a) != EOF)  //Read until EOF is returned.
  {
    sum += a ;
  }
  printf( "The total is %f \n", sum) ;

  fclose(fp) ;

  return 0 ;
}

上記のプログラムをコンパイル,実行すると以下のようになります.
The total is 1.998047 

プログラムを実行する際に,"input4.dat"がない場合は,以下のように表示されます.
File not found. : input4.dat 

2011年2月14日月曜日

[C/C++] ファイルからの入力

ファイルからデータを読み込むには以下の手順で行います.

  1. ファイルを入力して開く(オープン)
  2. データを読み込む
  3. ファイルを閉じる(クローズ)

ここで,オープンとは,ファイルを扱うための前処理を指し,クローズはファイル使用を終了する後処理を指します.これらの処理を行うには以下の形式で行います,以下でポインタ変数を fp とし,入力ファイル名を input.dat とします.

  FILE *fp ;

  if (( fp = fopen( "input1.dat", "r")) == NULL )
  {
    printf("File not found : input1.dat \n") ;
    exit(1) ;
  }

  データの読み込み部分

  fclose( fp ) ;

上記の形式にある

  fp = fopen( "input1.dat""r")

の部分がファイルのオープンに対応します.これは読み込み(Read)モード(r)でファイル(input.dat)をポープンするということを意味します.ファイルのオープンにはこの記述だけでも良いのですが,fopen関数は指定したファイルが存在しない場合は関数値としてNULLを返すので,ファイルが存在しない場合の処理を追加しておくのが一般的です.

if文の部分は,

  fp = fopen( "input1.dat""r") ;
  if (( fp == NULL )

と分けて書くことも可能です.上記の形式では,ファイルが存在しなければ exit かんすで処理を終了させるようにしています.

最後のfclose関数はファイルをクローズするための関数です.読み込みたいファイルがカレントディレクトリにない場合は,絶対パスもしくは相対パスでファイル名を指定します.

データを読み込むための関数でよく使われるのはfdcanf関数です.これはscanf関数と使い方がほとんど同じになります.

ファイルポインタ名を fp として,double型の変数 a をファイルポインタ fp に対応するファイルから読み込む場合の形式を以下に示します.scanf文と同様に変換文字を変更すれば,他の型の変数もファイルから読み込むことが可能です.fp は変数なので,必ずしも変数名をfp とする必要はありません(良く使用されるのは fin, fout, input, output など).

  FILE *fp ;
  double a ;

  fscanf(fp, "%lf", &a) ;

以下のようなinput1.datというファイルが用意されていて,それを読み込んで出力するプログラム例を以下に示します.

(input.dat)
1.0
2.0
3.0
4.0
-1.0

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  FILE *fp ;
  double a = 10.0 ; //Initialize with a number greater than 0.
  if (( fp = fopen( "input1.dat", "r")) == NULL )
  {
    printf("File not found : input1.dat \n") ;
    exit(1) ;
  }

  while (a > 0) //Since *a is initialized with a number greater than 0, it becomes true first.
  {
    fscanf(fp, "%lf", &a) ;
    printf("a = %f \n", a) ;
  }
  fclose( fp ) ;
  
  return 0 ;
}

上記のプログラムをコンパイル,実行すると以下のようになります.
a = 1.000000 
a = 2.000000 
a = 3.000000 
a = 4.000000 
a = -1.000000 

また,以下のようなinput2.datというファイルが用意されていて,上記のプログラムの input1.datをinput2.datに変更してコンパイル,実行すると以下のようになります.

(input.dat)
1.0 2.0
2.0 3.0
3.0 4.0
4.0 5.0
-1.0

a = 1.000000 
a = 2.000000 
a = 2.000000 
a = 3.000000 
a = 3.000000 
a = 4.000000 
a = 4.000000 
a = 5.000000 
a = -1.000000 

この例から分かるように,データの読み込みの際には,まず列(横)方向にデータを読み込み,データがなくなれば次の行に移ってデータを読み込むことがわかります.

続いて,行列のサイズが分かっている時に,ファイル(input3.dat)から各要素を読み込んで,そのデータを出力するプログラムを以下に示します.
(input3.dat)
1.0    -2.3    -12.5    235.5    
-3.0   13.5    145.3    -13.5    
13.23  453.0   33.5     113.0    

-3.0   13.5    145.3    -13.5    
1.0    -2.3    -12.5    235.5    
23.12  23.51   0.923    132.1

データを読み込む際には空行は無視されるので,input3.datのように空行があっても問題ありません.

#include <stdio.h>
#include <stdlib.h>

#define ROW     3
#define COLUMN  4

int main(void)
{
  FILE *fp ;
  double a[ROW][COLUMN] , b[ROW][COLUMN] ;
  int i, j ;

  if (( fp = fopen( "input3.dat", "r")) == NULL )
  {
    printf("File not found: input3.dat \n") ;
    exit(1) ;
  }

  printf("The matrix A is as follows. \n") ;
  for( i = 0 ; i < ROW ; i++)
  {
    for( j = 0 ; j < COLUMN ; j++)
    {
      fscanf( fp, "%lf", &a[i][j]) ;
      printf("%5.2f\t", a[i][j]) ;
    }
    printf("\n") ;
  }

  printf("The matrix B is as follows. \n") ;
  for ( i = 0 ; i < ROW ; i++)
  {
    for( j = 0; j < COLUMN ; j++)
    {
      fscanf(fp, "%lf", &b[i][j]) ;
      printf("%5.2f\t", b[i][j]) ;
    }
    printf("\n") ;
  }
  fclose( fp ) ;
  return 0 ;

}

上記のプログラムをコンパイル,実行すると以下のようになります.
The matrix A is as follows. 
 1.00 -2.30 -12.50 235.50
-3.00 13.50 145.30 -13.50
13.23 453.00 33.50 113.00
The matrix B is as follows. 
-3.00 13.50 145.30 -13.50
 1.00 -2.30 -12.50 235.50
23.12 23.51 0.92 132.10