平均値,分散

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;
}