金子邦彦研究室プログラミングC 言語によるアルゴリズムとデータ構造平均値,分散

平均値,分散

1 平均,分散の計算

標準入力から整数データを読み込んで,以下のような式(D1,D2,...Dn は入力データ)で表される算術平均,幾何 平均,調和平均,分散を求めることを考えます.

2 計算上の留意点

2.1 表現可能な数値の範囲

上の計算を行う上で,まず考えることとしてどのようなデータ型を扱うかとい うことを決める必要がある.これを正しく決めておかないと,正しいアル ゴリズムで計算を行っても間違った値の結果が得られるということが起ってし まうことがあります.
数値は大きく分けて整数,浮動小数点数とに区別されますが,さらにデータを 扱える範囲によって,整数の場合,short,int,long,また実数の場合, float,double,long doubleというデータ型に分けられます.それぞれのデータ 型で表現できる数値の範囲はバイト数によって決定される.例として整数の 場合, の範囲の数値表現が可能です.整数を表す型であるshort,int,longのそれぞ れがどれくらいの大きさの整数を扱えるのかというのは,使用するコンパイ ラによって異なってきますが,どのコンパイラにおいてもshort≦int≦longで あることは保証されている.また,実数の場合は, の範囲が表現可能で,サイズは,float≦double≦long doubleになる. 自分の使用しているコンパイラがそれぞれの型をどのサイズで使っているの かを調べるには,sizeof演算子を使うことができる. sizeof(型名)で指定した型のサイズが何バイトであるかを返してく れます.

2.2 オーバーフロー

上で示したように,それぞれの型には表現可能な範囲が決められているので, これを意識していないと,計算の途中でこの範囲を越えてしまうということ が起こり得ます.これをオーバーフローといいます.整数を使う場合,よく int型を使うが,平均値を求めることを考えた場合,入力データの型を intにしてその範囲内で入力データを与えたとしても,途中の合計を求める 計算でその上限を越えてオーバーフローが発生してしまうという可能性もあ ります.しかし,プログラムはエラーを出さないまま勝手に計算してしまう ため間違った結果を出してしまうことになる.そこで,このようなオー バーフローが予想される場合は,より大きなサイズのデータ型(long,double など)を使うようにします.

2.3 少数点以下切り捨て

整数の演算において除算を行う場合,除算結果の小数点以下は切り捨てられ ます.(整数)/(整数)の場合のみ,この小数点以下切り捨てが行われます. (整数)/(実数)や(実数)/(整数)の場合は実数型と して計算さ れるので結果に少数点以下の値も含まれます.例として,2/3では結果は0になりますが, 2/3.0や2.0/3とすると結果は0.666...と なる.このように整数のみを含む演算はint型,浮動小数点数を含む演 算はdouble型として行われ,異なる型を含む演算では,必要に応じて自動的に型変 換が行われます.そこで,プログラムで式を記述する際は, 予期しない結果を招かないためにも明示的に型を指定する習慣をつけましょ う.型の変更を行う時は,(変 換する型名)値,または(変換する型名)変数,とすることで値,変数を指定した 型に変換できる.(double)2/3や2/(double)3とすれば0.666...という結果 が得られます.

2.4 0除算

ある数を0で割る(0除算)と,プログラムは異常終了してしまいます.調和 平均を求める場合,入力データが分母になっていますので,入力データに0が 含まれると,0除算が起こり異常終了します.これを避けるには,入力デー タの0を検知して,調和平均の計算を止めるようにします.

3 コード

3.1 算術平均,幾何平均,調和平均の計算

入力データは整数ですが,途中の計算値(加算,乗算)を格納する変数には, オーバーフローを避けるためdouble型を使用する.また,入力データに0が 含まれた場合,調和平均が計算できないことを示すメッセージを出力するよう にします.
#include<stdio.h>
#include<math.h>

const int BUFFER_SIZE = 100;

main()
{
    int data;                   /* 入力データ */
    int num = 0;                /* データ数の記憶 */
    int zero = 0;               /* 0を検知するための制御変数 */
    double arithmetic = 0.0;    /* 算術平均 */
    double geometric = 1.0;     /* 幾何平均 */
    double harmonic = 0.0;      /* 調和平均 */
    int n, i;
    char buf[BUFFER_SIZE], c;

   /* データを読み込み,加算,乗算,0検知を行う.データが整数である限り繰り返す.*/
    printf("Please input data : ");

    while(true){
    /* データ(1行分読み込み) */
    fgets(buf,BUFFER_SIZE,stdin);

    /* データ取り出しと計算 */
    n = sscanf(buf,"%d %c",&data,&c);

    if( ( n == -1 ) || ( n == 0 ) ) {
        break;
    }
    else if(n == 1) {
        // the number of data is 1.
        num++;
        arithmetic = arithmetic + (double)data;
        geometric = geometric * (double)data;
        if(data == 0) {
        zero = 1;
        }
        if(zero == 0) {
        harmonic = harmonic + 1.0 / (double)data;
        }
    }
    else if(n == 2) {
        // the number of data is larger than 2.
        i = 0;
        while(true) {
        num++;
        arithmetic = arithmetic + (double)data;
        geometric = geometric * (double)data;
        if(data == 0) {
            zero = 1;
        }
        if(zero == 0) {
            harmonic = harmonic + 1.0 / (double)data;
        }

        // 空白文字を読み飛ばす
        while(true) {
            if(buf[i] == ' ') {
            i++;
            }
            else {
            break;
            }
        }

        // 数字(0から9)を読み飛ばす
        while(true){
            if(buf[i] >= '0' && buf[i] <= '9') {
            i++;
            }
            else {
            break;
            }
        }

        // 行末なら終える
        if(buf[i] == '\n') {
            break;
        }

        // データ読み込み.読み込みに成功したら次の文字へ(i++), 失敗したら終える(break).
        n = sscanf(buf+i,"%d",&data);
        if(n == 0) {
            break;
        }
        i++;
        }
    }

    if(n == 0) {
        break;
    }
    }

    printf("算術平均 = %lf\n",arithmetic / (double)num);
    printf("幾何平均 = %lf\n",pow(geometric,1.0 / (double)num));
    if(zero == 0) {
    printf("調和平均 = %lf\n",(double)num / harmonic);
    }
    else {
    printf("調和平均 = 無限大(0除算)\n");
    }

    return 0;
}

3.2 分散

分散を計算するためには,まず平均値を求め,その後個々の入力データと平均 値の差の2乗を計算するので,個々の入力データの値を記憶しておく必要があ ります.そこで,入力データを格納するために配列を使う.
#include<stdio.h>
#include<math.h>

const int MAX = 10;
const int BUFFER_SIZE = 100;

main()
{
    int data;                    /* 入力データ */
    double inputdata[MAX];       /* 入力データを格納する配列 */
    int n, i;
    int num = 0;
    double M = 0.0;              /* 算術平均 */
    double var;                  /* 分散 */
    char buf[BUFFER_SIZE],c;

    /* データを読み込み,加算,配列への格納を行う */
    printf("Please input data : ");

    while(true){
    /* データ(1行分読み込み) */
    fgets(buf,BUFFER_SIZE,stdin);

    /* データ取り出しと計算 */
    n = sscanf(buf,"%d %c",&data,&c);

    if( ( n == -1 ) || ( n == 0 ) ) {
        break;
    }
    else if(n == 1) {
        // the number of data is 1.
        inputdata[num++] = (double)data;
        M = M + (double)data;
    }
    else if(n == 2) {
        // the number of data is larger than 2.
        i = 0;
        while(true){
        inputdata[num++] = (double)data;
        M = M + (double)data;

        // 空白文字を読み飛ばす
        while(true){
            if(buf[i] == ' ') {
            i++;
            }
            else {
            break;
            }
        }

        // 数字(0から9)を読み飛ばす
        while(true){
            if(buf[i] >= '0' && buf[i] <= '9') {
            i++;
            }
            else {
            break;
            }
        }

        // 行末なら終える
        if(buf[i] == '\n') {
            break;
        }

        // データ読み込み.読み込みに成功したら次の文字へ(i++), 失敗したら終える(break).
        n = sscanf(buf+i,"%d",&data);
        if(n == 0) {
            break;
        }
        i++;
        }
    }
    if(n == 0) {
        break;
    }
    }
    M = M / (double)num;

    /* 分散の計算(入力されたデータ数の回数だけ繰り返す)*/
    for(var = 0.0 , i = 0 ; i < num ; i++){
    var = var + pow((inputdata[i] - M),2);
    }
    if(num <= MAX){
    printf("算術平均 = %lf\n",M);
    printf("分散 = %lf\n",var / (double)num);
    }
    else {
    printf("入力を%d個以下にしてください。\n",MAX);
    }

    return 0;
}