• ベストアンサー

たくさんの数の平均を求める方法について

どうもこんにちは 研究でシミュレート用のプログラムを書いています 大量の数を入力し、その平均値を求めるコードを書いているのですが、 誤差ができるだけ小さくなる方法はないでしょうか 入力する数はdouble型の実数値あるいはint型の整数値で、 個数は1億程度です。 最初は1つずつ足していたのですが、整数型の場合はオーバーフローしてしまい、実数型の場合も徐々に加算する値が相対的に小さくなり、誤差が大きくなっていきました。 100万個ずつに区切って平均を求め、それを後で合計する方法も考えましたが、あまりきれいな方法になりません なにかいい方法はないでしょうか

質問者が選んだベストアンサー

  • ベストアンサー
  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.3

情報落ちを避ける方法は存在します. 誰が考案した方法なんだったっけ....

参考URL:
http://www.cc.kyoto-su.ac.jp/~yamada/pB/float.html#johouot
miki_rise
質問者

お礼

回答ありがとうございます。 この処理も試してみようと思います。 桁数に極端な差がある場合は厳しいかもしれませんが。。。

全文を見る
すると、全ての回答が全文表示されます。

その他の回答 (12)

  • SaKaKashi
  • ベストアンサー率24% (755/3136)
回答No.13

一億個ですか。基本的に桁落ちを防止するには、小さい値から加算します。 可能なら、値を小から大の順に並べて加算します。

miki_rise
質問者

お礼

締め切りました。

miki_rise
質問者

補足

それも考えてみましたが、ソートに時間がかかり採用できませんでした。

全文を見る
すると、全ての回答が全文表示されます。
  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.12

#10です。 int型より更に精度が上がる方法が有りました。 double型は整数値であれば15桁まで、正確に格納できます。 つまり、999999999999999(9が15個)は、正確な数値として保持できます。 double型の合計を2つ用意します。 一方は小計用、他方は合計用とします。 どちらも0でクリアして開始します。 小計用に加算し続けます。 小計が99999999999999(9が14個)を越えたとき、その値を 合計に加算し、小計を0クリアします。 加算した時の小計は999999999999999(9が15個)以下のはずので、小計は 正確な数値を保持しています。 上記を最後まで行い、最後に小計を、合計に加算します。 合計には、今までの数値の総和が格納されています。

miki_rise
質問者

お礼

回答ありがとうございます。 2つの変数を使用して、桁を区切って加算する方法も考えては見ました。 検討しようと思います。

全文を見る
すると、全ての回答が全文表示されます。
回答No.11

#6で回答した者です。別法です。 合計を求める再帰関数 double sum( double x[], int i, int j) { int k; if ( i>=j ) return x[i]; k=i+(j-i)/2; return sum(x, i, k)+sum(x, k+1, j); } を使って average=sum(x, 0, n-1)/n; で求めるというのはどうですか。 ループで求める場合の10倍ぐらいの所要時間がかかるかもしれませんが データ数が1億個でも数秒以内でできると思います。

miki_rise
質問者

お礼

回答ありがとうございます。 再帰処理は考えていませんでした。 というのも、たぶんクライアントからOKがでないと。。。 それとは別の機会に使用するかもしれないので覚えておきます。

全文を見る
すると、全ての回答が全文表示されます。
  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.10

>> それとも、int型かdouble型を使用しなさいという制約があるのでしょうか? >そうです。 それでは、以下のような方法はいかがでしょうか。 合計を求める領域をint型とdouble型で用意します。(最初に0クリアしておきます) int型に加算を繰り返していきますが、もし加算した結果がオーバーフローする場合は、加算する前のint型の値を、double型に加算します。 そして、int型を0クリアのち、int型に加算します。 上記の処理を繰り返していきます。 全て加算した後に、最後にint型の合計をdouble型に加算します。 そうするとdouble型に全ての合計が格納されています。 尚、オーバーフローしたかどうかは、加算前と加算後の値の大小を比較すれば判ります。加算前>加算後の場合、オーバーフローが起こっていると判断します。

miki_rise
質問者

お礼

締め切りました。

全文を見る
すると、全ての回答が全文表示されます。
  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.9

>int64 は使用できないのです。 お使いのコンパイラとOSは何でしょうか。 たぶん使えると思いますが・・・・ それとも、int型かdouble型を使用しなさいという制約があるのでしょうか?

miki_rise
質問者

お礼

締め切りました。

miki_rise
質問者

補足

> それとも、int型かdouble型を使用しなさいという制約があるのでしょうか? そうです。

全文を見る
すると、全ての回答が全文表示されます。
回答No.8

No.5 の回答者です。 1億個足すと精度が下がるとのことですが、 No.5のやりかたは、普通に足すのにくらべて31bit多く精度があります。 でも今きずいたけど、unsignedにしか対応してないな・・・。 signedに対応すると・・・。こんな感じ。 int i,j; double a[32],b; int indt,abs_dt; int msk =0x4000000; for( i=0;i<32;i++) a[i]=0.; for (i=0;i<100000000;i++){ indt = rand(); //入力 abs_dt = (indt<0) ? ~indt : indt; for(j=0;j<31;j++){ if(abs_dt & (msk>>j)){ a[j] += indt; break; } } } for(i=0;i<32;i++) b+=a[i]; out = b/100000000.; 条件分岐が多いのなら、1ビット毎に分けているのを2ビットごととかにすれば、少なくなります。

miki_rise
質問者

お礼

回答ありがとうございます。 もう少し検討してみようと思います。

全文を見る
すると、全ての回答が全文表示されます。
  • usokoku
  • ベストアンサー率29% (744/2561)
回答No.7

>計算量が極端に多くなる処理は使えないのです。 だから、障害発生時のみの例外処理のある計算方法を答えたでしょう。 配列のアドレス(ポインター)の計算が増えますが、主記憶を内部レジスターに使っていたCPUが昔ありましたので、それぼとむちゃくちゃな計算方法ではありません。 >変数もintやdoubleなど基本のものしか使えません。 1ライン アセンブラ は使えませんか。補助レジスター(自由に使えるのは2つか3つですが)を使えば、今のCPUは32bitなので64-98bit演算が可能。 インテル系ならばSP, BP, IPを保護して、SI, DIを入出力ポインターに割り振り、EAXからEDXの4レジスターを使って128bit演算が可能なはず。

miki_rise
質問者

お礼

締め切りました。

全文を見る
すると、全ての回答が全文表示されます。
回答No.6

 ほぼ同じ値の実数値が多数個あるなら、最初、単純に合計を求めて、それが大きな誤差を含んでいても、それを使って平均値を求め、次に、元の各データとこの平均値との差の合計を求める。それに先ほどの平均値をデータの個数倍したものを加えれば、もう少し精度が上がります。  別のやり方としては、最初データを隣り合った2つずつの組にして、それぞれの組の合計を、もとのデータ数の半分の大きさの配列の各要素に入れる、というのを繰り返せば、配列の長さが1回ごとに半分になり、最後は1になって、元の全データの合計が得られる、というのはどうですか。

miki_rise
質問者

お礼

回答ありがとうございます。 一度平均を求めてからあらためて平均値を求めるやり方は確かに精度が上がりました。 これも試してみようと思います。 2つずつ加算していく方法は考えましたが、処理がややこしくなりそうなので試してませんでした。 こちらも試してみようと思います。

全文を見る
すると、全ての回答が全文表示されます。
回答No.5

例えば入力がint型 indtという変数で int型が32bitなら double a[32],b; for(i=0;i<32:i++) a[i]=0.; if(indt&0x80000000) a[0] += indt; else if(indt&0x40000000) a[1] += indt; else if(indt&0x20000000) a[2] += indt; else if(indt&0x10000000) a[3] += indt; else if(indt&0x08000000) a[4] += indt; else if(indt&0x04000000) a[5] += indt; else if(indt&0x02000000) a[6] += indt; else if(indt&0x01000000) a[7] += indt; ... else if(indt&0x00000002) a[30] += indt; else a[31] += indt; for(i=0;i<32:i++) b+=a[i]; でどうにかなるんでは? つまり入力の精度によって、合計を求める変数を変えれば桁おちしにくいかも。

miki_rise
質問者

お礼

締め切りました。

miki_rise
質問者

補足

a[0] に1億個とか値を加算すればa[0]の値が相対的にindtより大きくなっていき、精度が落ちていくと思います。 それに高々1つの値を加算するのに条件分岐を大量に使うのは避けたいです。

全文を見る
すると、全ての回答が全文表示されます。
  • usokoku
  • ベストアンサー率29% (744/2561)
回答No.4

2番です。 3番の方の「情報落ち」の意味で「桁落ち」という言葉を使っています。 加算だけならば、整数の固定長100桁10進演算専用ライブラリをご自身で作っても、半日ぐらいでできるでしょう。10進ですとそのままテキストかできますので、計算時間が多少かかってもよい場合(せいぜい10回程度しか使わないで捨てるソフト)なら、考慮の対象に入れてもよいでしょう。

miki_rise
質問者

お礼

締め切りました。

miki_rise
質問者

補足

計算時間が多少かかるのは避けたいのです。

全文を見る
すると、全ての回答が全文表示されます。

関連するQ&A

  • 2進数の減算のオーバーフロー/アンダーフロー

    2進数の減算のオーバーフロー/アンダーフロー 2進数の加減算においてどういうときにオーバーフローするのかわかりません。 例えば、 符号付き整数加算として、 1111+0001=10000 となり、4ビットで表現しきれないので、この場合オーバーフローということでしょうか? 基本的に、元々のビット列の長さ(この場合、4ビット)を演算後、超えてしまう 結果となった場合、オーバーフロー、アンダーフローが起きていると考えてしまって よいのでしょうか? 10進数に変換して10進数の演算結果と異なることが分かれば、オーバーフローが 起きているといえるのでしょうが、ビット長が100ビットなど多ビット長の場合に そのようなことはできないので、簡単なオーバーフロー、アンダーフローの見分け方が 知りたいです。 ご回答お願い致します。

  • 負の数の四捨五入の方法

    はじめまして。大学で週一でC言語を習っている全くの初心者です。 過去ログを確認したのですがなかったようなので質問させていただきます。 タイトルの通りなのですが「入力される実数が負の数の場合」に四捨五入するプログラムを作れ!とのことなんですが・・・。 正の数の四捨五入のプログラムは作りましたが、逆がわかりません。 ------------------------------------------------------------- //入力された正の実数を四捨五入して整数にするプログラム #include <stdio.h> int main(void) { int seisu; double jissu; printf("実数="); scanf("%lf" ,&jissu); seisu =jissu + 0.5; printf(" %lf の四捨五入は %d です。\n",jissu,seisu ); } ------------------------------------------------------------ ちょこっと変えればいいのかなぁ・・・とは思うのですが・・・。 ご教授お願いします。 ちなみにOSはXP、使っているコンパイラはbcc32というやつです。

  • 負の整数になるまで入力→その平均を出力という問題なんですが

    わからないので教えてください。 キーボード入力を負の整数になるまで繰り返し、負の整数を入力したら 負の整数を含めずにその平均値を出しなさい。 という問題なのですが、 僕は繰り返しなので、 do{ System.out.println("整数を入力してください。負の数が入力されたら終了します。"); line = br.readLine(); i= Integer.parseInt(line); }while(i>0); と考えたのですが、これ以上先に進めなくて困っています。 まずこのiに入力されたものを合計しなくてはいけないので、配列を使わないとならないとわかったのですが、配列の使い方がいまいちわかりません。 int []m=new int[i]; と入力し(その前にiを初期化) m[i]= Integer.parseInt(line); としたのですが、うまくいきませんでした。この場合どのように配列を考えればよいのでしょうか。 あと、「最後の入力した負の整数を含まず」ということは、どう書けばよいのでしょうか。 ヒントでもいいので教えてください。よろしくお願いします。

    • ベストアンサー
    • Java
  • 2進数の1の数を数える問題

     次の問題に対して、以下のソースを考案し、実行したところ、以下のようになりました。 【問】 ◆◆◆◆◆  与えられた10進数の整数Nを2進数に変換したときの1の個数を答えよ。  整数の10進数を2進数に変換するには、変換したい10進数を商が0になるまで2で割り続け、求めた余りの部分を逆順に並べる。 例) 13が入力として与えられた場合、以下のように2で割り続け2進数を求める。 13 / 2 = 6 ・・・余り 1 6 / 2 = 3 ・・・余り 0 3 / 2 = 1 ・・・余り 1 1 / 2 = 0 ・・・余り 1  求められたそれぞれの余りを逆順に並べたものが2進数への変換結果となる。  よって入力が13ならば2進数への変換結果は 1101 になり、1は3個あるので出力は3になる。 [入力例1] 13 [出力例1] 3 [入力例2] 45 [出力例2] 4 ◆◆◆◆◆ (自分の解答、□はタブ) ◆◆◆◆◆ #include <stdio.h> #include <stdlib.h> /* EXIT_SUCCESS */ int main(void) { □int n, bit, s; □scanf("%d", &n); □ □bit = n % 2; □n /= 2; □s = 0; □while(n) { □□if (bit) □□□s++; □bit = n % 2; □n /= 2; □} □printf("%d", s); □return EXIT_SUCCESS; } ◆◆◆◆◆ (実行結果1) ◆◆◆◆◆ [入力] 13 [出力] 2 ◆◆◆◆◆ (実行結果2) ◆◆◆◆◆ [入力] 45 [出力] 3 ◆◆◆◆◆ となり、正解となりません。  自分の考えとしては、入力された数値を変数nに収め、これをどんどん2で割っていき、そのときの余り(0か1)を変数bitに収めていき、nが0になるまで処理を繰り返し、bitが0でないとき、変数s(初期値0)に1ずつ加算していき、最後にsを出力する、という方針です。  初期値の設定辺りが間違っているのだと思いますが、これ以上いくら考えても正解が得られません。どこがどう間違っているのでしょうか。どなたかご教授頂きたく、お願い致します。

  • 平均の出し方について

    プログラミング始めたばかりで簡単なことで戸惑っています。 例えば二つの整数の平均を求める場合 int a,b; printf("%d%d\n",a,b); scanf("%d%d",&a,&b); このあとどのようにすれば出せるのでしょうか。 私はprintf("%d\n",(a+b)/2)と入力するのですがうまくいきません。 よろしくお願いします。

  • 数学得意な人に質問です。平均の平均について

    値の個数nで割った普通の相加平均と、値の個数nを分割して平均して更にその平均値を相加平均した場合は同じ値になるのでしょうか。 例えば10,000個の数字を全部加算して10,000で割った平均と、10個ずつで相加平均をだし、その平均値1000で更に相加平均をすれば理論的には同じ値になるのでしょうか?

  • 配列変数を用いて平均値を求める

    こんにちわ 実は、入力データを使って平均値を求めるプログラムについてなんですけど。 //データの平均値の計算 #include <stdio.h> void main () { int i, k, n, x[100]; double a; while (1) { a = 0; printf("データ数を入力してください..."); scanf("%d",&n); printf("正の実数のデータを入力してください。"); for (i = 0;i < n ;i++) scanf("%d",&x[i]); for (k = 0;k < n;k++) a += x[k]; printf("平均値 %f\n", a/n); } } すいません、インデントがいい加減です。  こうやると普通に平均値を出せますが、最初にデータ数を入力しなければいけません。  私は、データの個数を入力しないで平均値を出したいと思います。どういう風に上のプログラムを変更すればできるのでしょうか?上のプログラムに誤植があるとは思いますが。。。  また、たとえば最後に0以下の数字を入力したらデータ入力を終わらせる...  if(x[i]<=0)break; はどこに入れればいいでしょうか?  たくさんの質問すいません。ご教授よろしくお願いします。

  • 【JAVA】配列の要素の平均について

    JAVAで質問となります。 以下のプラグラムを教えていただきたいです。 ____________________ n 個の要素で構成される int 型の配列を引数として受け取り,配列内の全要素の平均値を返すメソッドを作成してください. また,コマンドライン入力に整数値を入力すると,作成したメソッドを使用して,入力した整数値の平均値を求め,結果を標準出力に出力するプログラムを作成してください. このプログラムを作成する際は,以下の要件を満たすように作成してください:  【メソッド】 配列内の全要素の平均値を求めるメソッドは,以下の仕様とする: メソッド名:calcAverage 引数:配列 - int 型の配列 戻り値:平均値 - double 型 内容:引数として受け取った配列のすべての要素の平均値を計算する.  【入力】 整数値のデータは,コマンドライン入力に入力する. その際,各整数値は,半角スペースで区切る.  【出力】 コマンドライン入力に1つ以上の整数値が入力された場合は,それらの平均値を出力する. コマンドライン入力に何も入力されなかった場合は,何も出力しない. 平均値の出力は,全体を左詰めで小数点以下2桁まで出力する. 行の先頭には空白を入れず,行の左端から出力する.

  • 下記AとBがあるとして、それぞれ係数/実数の平均を出したい。

    下記AとBがあるとして、それぞれ係数/実数の平均を出したい。 A 実数 係数 10 5 20 4 B 実数 係数 20 2 40 3 そのとき、Aの平均を出す場合には、 (1)30%=(5+4)/(10+20) (2)35%=((5/10)+(4/20))/2 Bの平均を出す場合には、 (3)8.3%=(2+3)/(20+40) (4)8.8%=((2/20)+(3/40))/2 どの計算式が一般的なのでしょうか? もう一つ、全体の平均を出すには、 (5)19.2%= ((1)+(3))/2 (6)21.9%= ((2)+(4))/2 (7)15.6%= (5+4+2+3)/(10+20+20+40) (8)21.9%= ((5/10)+(4/20)+(2/20)+(3/40))/4 どの計算式が一般的なのでしょうか? 最後に、割り算を含む二つの結果を平均出すときに誤差が生じるのですが、この言い方ってあるのでしょうか?(何とかの法則とか よろしくお願いします。

  • 休業日を除いて1日平均を計算する関数

    お世話になります。日計表の作り方を教えてください。 ・日付はあらかじめ31日まで入力しておくものとします。 ・曜日は入力されていません。   ついたちの曜日を入力すると、以下繰り返して自動入力できたりしないでしょうか? ・本日の売上個数を手入力します。 ・累計個数を計算しています。 ・1日平均の関数を入れました。「=SUM(D3) / COUNTIF(D3, "<>0")」 ・営業日数をカウントしたい。 ■土日祝は営業していないということです。 ■1日平均を計算したいのですが、休業日(個数がゼロ)もカウントしてしまうため、 1日平均の数値が少なくなってしまいます。休業日は1日平均から除外したい。 ■営業日をカウントしたいのですが「個数が入力されている日は営業日として加算する」 ということはできないでしょうか。

このQ&Aのポイント
  • 現在派遣社員をしている方々は、最初から社会保険に加入していると言われますが、自分は加入していません。
  • 派遣会社の担当者からは、社会保険に加入すると時給が下がると言われていますが、疑問に思っています。
  • 派遣会社からの連絡で、9月から社会保険に加入していたことにしてほしいと言われました。加入していた国保保険料の返還や医療機関への支払い訂正についても心配です。どうすればよいか迷っています。
回答を見る