見出し画像

プログラミング学習の記録 #015(C)

C言語の学習は、教科書的には今回が最終となった。とりあえず、基本的なC言語の操作は、一通り学習できたと思う。実際に数値計算を行ううえでは、より高度な技術を習得していく必要があると思うが、それらについて学習するための基礎は、習得できただろう。今後は、C言語のより発展的な内容を学習していくとともに、他のプログラミング言語の学習も進めていきたいところである。

前回の復習

練習問題 12.1

複素数の加算、減算、割算を行う関数を作成し、積も含めて実際に複素数演算を行ってみよ。

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

typedef struct complex
{
    double x,y ;
} comp;

comp plus(comp a, comp b)
{
    comp w;
    w.x = a.x + b.x ;
    w.y = a.y + b.y ;
    return w;
}

comp minus(comp a, comp b)
{
    comp w;
    w.x = a.x - b.x ;
    w.y = a.y - b.y ;
    return w;
}

comp times(comp a, comp b)
{
    comp w;
    w.x = a.x * b.x - a.y * b.y ;
    w.y = a.x * b.y + b.x * a.y ;
    return w;
}

comp divide(comp a, comp b)
{
    comp w;
    b.y = - b.y ;
    w = times(a,b) ;
    w.x = w.x / ( pow(b.x,2) + pow(b.y,2) ) ;
    w.y = w.y / ( pow(b.x,2) + pow(b.y,2) ) ;
    return w;
}

void result(comp w)
{
    if(w.y < 0)
    {
    printf("%7.2f - %7.2f i\n",w.x,-w.y);    
    }else
    {
        printf("%7.2f + %7.2f i\n",w.x,w.y);
    }
}

int main()
{
    comp a,b ;
    comp p,q,r,s;
    int si;
    si = 0 ;

    printf("Complex numbers x+iy.\n");
    printf("You should input the numbers; x y.\n");
    printf("Input the complex number1: ");
    scanf("%lf %lf",&a.x,&a.y);
    printf("Input the complex number2: ");
    scanf("%lf %lf",&b.x,&b.y);

    p = plus(a,b) ;
    q = minus(a,b) ;
    r = times(a,b) ;
    if(b.x == 0 && b.y == 0)
    {
        si = 1 ;
    }else
    {
        s = divide(a,b) ;
    }

    printf("a   = ");
    result(a);
    printf("b   = ");
    result(b);
    printf("a+b = ");
    result(p);
    printf("a-b = ");
    result(q);
    printf("a*b = ");
    result(r);
    printf("a/b ");    
    if(si == 0)
    {
        printf("= ");
        result(s);
    }else
    {
        printf("is error.\n");        
    }

    printf("HAPPY SMILE (^_^)v\n");
    return 0;
}

関数の再帰呼び出し

C言語では、関数の中で、その関数自身を呼び出すことができる。たとえば、

$$
n!
= n \cdot (n-1) \cdot (n-2) \cdot \cdots \cdot 2 \cdot 1
= n \cdot [(n-1)!]
$$

と式変形できる。教科書を参考にして、関数の再帰呼び出しを行いつつ、ある自然数$${n}$$の階乗を求めるプログラムコードを書いた。

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

int factorial(int n)
{
    int r;

    if(n > 1)
    {
        r = n * factorial(n-1);
    }else if(n == 1)
    {
        r = 1 ;
    }else
    {
        r = 0;
    }

    return r;
}

int main()
{
    int n,r;

    printf("Input the number n(>=0): ");
    scanf("%d",&n);
    if(n < 0)
    {
        printf("Error in n.\n");
        exit(1);
    }

    r = factorial(n);

    printf("%d! = %d\n",n,r);
    printf("HAPPY SMILE (^_^)v\n");
    return 0;
}

ファイル処理

C言語でファイルの入出力を行うときには、以下の手順での操作が必要となる。

  1. ファイルを開く

  2. ファイルにデータを書き込む/ファイルからデータを読み取る

  3. ファイルを閉じる

これらの操作は、FILE型ポインタを用いて行う。

fopen

ファイルを開くときにはfopenを用いる。

FILE *sample1, *sample2;
sample1 = fopen("data1.txt","w") ;
sample2 = fopen("data2.txt","r") ;

fopenには、いくつかのモードがあり、基本的に、書き込むときには「w」、読み取るときには「r」を選択する。fopenのモードは、以下の通りである。

  • r  データ読み取り

  • w  データ書き込み

  • a  データ追加書き込み

  • r+  データ読み取り・書き込み、ファイルが存在しなければNULL返却

  • w+  データ読み取り・書き込み、ファイルが存在しなければ新規作成、ファイルが存在すれば上書き

  • a+  データ追加書き込み・読み取り

fprintf

ファイルにデータを書き込むときにはfprintfを用いる。

fprintf(sample1, "%f %f\n",x,y);

書き込み先のファイルのポインタを指定し、書き込む内容を記載する。printfと似た方法で用いる。

fscanf

ファイルからデータを読み取るときにはfscanfを用いる。

fscanf(sample2, %lf %lf",&a,&b);

fclose

ファイルを閉じるときにはfcloseを用いる。

fclose(sample1);
fclose(sample2);

数値計算における注意事項

桁落ち

数値計算を行ううえで、計算精度の問題を意識する必要がある。たとえば、$${a=1.0000001, b=1}$$としたときに、$${a-b=0.0000001}$$となるはずである。この計算を行うプログラムコードを書いた。

#include <stdio.h>

int main()
{
    float a,b,c;

    a = 1.0000001 ;
    b = 1 ;
    c = a - b ;

    printf("a   = %15.14f\n",a);
    printf("b   = %15.14f\n",b);
    printf("a-b = %15.14f\n",c);

    printf("HAPPY SMILE (^_^)v\n");
    return 0;
}

これを実行すると、以下のように表示された。

% ./a.out
a   = 1.00000011920929
b   = 1.00000000000000
a-b = 0.00000011920929
HAPPY SMILE (^_^)v

つまり、$${a=1.0000001}$$では有効数字が7桁であったが、$${a-b=1 \times 10^{-7}}$$では有効数字が1桁になっている。本来、float型は、約7桁の計算精度があるが、近い値の減算によって、計算精度が小さくなったということである。これを桁落ちという。ちなみに、double型で計算すると、実行結果は、以下のように表示された。

% ./a.out
a   = 1.00000010000000
b   = 1.00000000000000
a-b = 0.00000010000000
HAPPY SMILE (^_^)v

桁落ちについての学習は、このサイトこのサイトを参考にした。

積み残し

級数和の計算において、相対的に極端に小さい数を加えるときに、正しく計算されないことがある。教科書を参考にして、初期値を入力し、微少な数を1000回加える計算を行うプログラムコードを書いた。

#include <stdio.h>

int main()
{
    double sum, dx;
    int i,n;
    n = 1000 ;
    dx = 1.0e-16 ;

    printf("Input initial number: ");
    scanf("%lf",&sum);

    for( i = 0 ; i < n ; i = i + 1 )
    {
        sum = sum + dx ;
    }

    printf("sum = %16.15f\n",sum);

    printf("HAPPY SMILE (^_^)v\n");
    return 0;
}

これを実行して、初期値を0,1にすると、それぞれ以下のように表示された。

% ./a.out
Input initial number: 0
sum = 0.000000000000100
HAPPY SMILE (^_^)v
% ./a.out
Input initial number: 1
sum = 1.000000000000000
HAPPY SMILE (^_^)v

初期値0のときには正しく加算されているが、初期値1のときには加算が正しく行われていない。これを積み残しという。積み残しの問題は、小さい値から演算していくことで解決できることがある。

計算速度の向上

数値計算においては、実質的に同じ演算であっても、計算速度が異なることがある。以下のような事例が挙げられる。

  • 乗算と除算は、加算と減算よりも多くの計算時間を要する。

  • 数学関数や組込み関数は、単純な演算よりも多くの計算時間を要する。

ただ、関数を使用しないと、コードが見にくくなってしまうことがある。数学関数などを用いたコードを書いて実行し、正しく実行されてから、計算速度を向上させるようにコードを書き換えるとよい。

-----

動け!タイムライン

この記事が参加している募集

#今日やったこと

30,933件

動物園か水族館にいきたいですね。