• ベストアンサー

計算に誤差が出る?

0.1 + 0.2 + 0.3・・・・・・ このように行うプログラムを2通りに分けて処理をして見ました。 以下にソースを載せます。 #include <stdio.h> int main() { float sum, i; float sum2; int f; for(i=0.1, sum=0.0; i<=100.0; sum+=(float)i, i+=0.1); printf("%f\n", sum); for(f=1, sum2=0.0; f<=1000; sum2+=(float)f/10.0, f++); printf("%f\n", sum2); return 0; } 初めのfor文と2番目のfor文では同じ処理を行っているのですが、計算結果が微妙に異なって出力されてしまいました。 理由が分かる方は教えてもらえないでしょうか?

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

  • ベストアンサー
回答No.5

No.3 です。 投稿に少々間違いがあって、本当は、6 の時ではなくて、3 で既に誤差が明らかになりますね。正しくは、1.00 と 0.999 です。 さて、 > しかし、分からない部分が一箇所。2列目は0.333自体をどん > どん足していく処理で、3列目は1/3の結果を毎回足しています。 これが実は誤解です。 3列目は、1/3 を足しているのではなく、「1ずつ足したものを毎回3で割っている」のです。 ここにはいろいろな形の誤差が現れています。 まず、1/3 が正確に表現できないので、0.333 で近似しています。この時点で、0.0003333... という大きさの誤差が出ます。これがいわゆる打ち切り誤差(丸めの誤差)です。 3列目の各項は 1, 2, 3, 4... という「誤差のない」数字を3で割っていますから、この打ち切り誤差だけが表面に出てきています。 これに対して、2列目は、0.333 という誤差のある数字を順次足していますから、最初の打ち切り誤差が累積します。 打ち切り誤差の大きさは、「最後の桁に対して、±1以下」です。 つまり、1/3 を 0.333 と表現した場合、この誤差は、0.001 を超えることはありません。3列目に現れている誤差はこの部分だけなので、各項は、最初の整数(1, 2, 3 ... )を3で割った値の「最後の桁に対して±1以下」の誤差です。 ですから、小数点が3桁確保できている範囲では、誤差は 0.001以下になっています。これに対して2列目は、0.00033333 という大きさの誤差が各項ごとに累積されます。 こういうことです。 以下おまけ。 さらにもうひとつ「情報落ち」という誤差が出てきます。 これは、ここに示した範囲では明らかにはなっていませんが、もしも、このまま計算を進めて 300行を超えると、明らかになります。 つまり、有効数字3桁の世界では3列目の方法では 300/3 = 100, 301/3 = 100, 302/3 = 100, 303/3 = 101 となって、誤差を伴いながらも、数字は変化します。しかし2列目の方法では、 100 + 0.333 = 100 のままですから、ここを超えると永久に数字が変化しなくなります。 このように、足す数と足される数の違いが大きいとさらに誤差が出てくることになります。

noconan
質問者

お礼

返答ありがとうございました。ためしに以下のプログラムを作成しました。 for(i=0.333, sum=0.0; i<=3; sum+=(float)i, i+=0.333){ printf("%.3f\n", sum); } for(f=1, sum2=0.0; f<=10; sum2+=(float)f/3.0, f++){ printf("%.3f\n", sum2); } AsanoNagiさんがおっしゃられたとおりになりました。 少しまとめていってしまえば上のfor文では毎回0.0003333の誤差が累積していくのに対し、下のfor文では0.3333333333333333・・・と表現できるところまでの少数桁を毎回加算して、表示のときに四捨五入をして 切り詰めて表示をしているため、まず3回目のところで、1.000と0.999の値が出てしまうんですね。

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

その他の回答 (4)

noname#30727
noname#30727
回答No.4

IEEE754に限定すると、0.1は表現不可能な値です。 2進数で表現すると 0.000110010101010101010101010 0.000110010101010101010101011 この2つの値の間に0.1があり、0.1は2進数では無限小数です。 我々にとってのキリのいい少数は大抵表現できないので、誤差は出るものと思うしかありません。

noconan
質問者

お礼

返答ありがとうございました。 固定小数点の場合は確かにおっしゃられる通りになりますね。 0.1を固定小数点の2進数表現で表す場合、2をかけていけば4回目でやっと1が小数点でなくなるので、記述されたとおりになりますね。 ぎゃくにコンピュータで正確に表現できる数は、0.5, 0.25, 0.75・・・などですね。 アドバイスを頂きありがとうございました。

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

これは、2進数表現の浮動小数点の世界では、 1)÷ 10 というのは、たいていの場合割り切れない 2)2進数表示で「有効桁数」が決まっている ことによります。 慣れ親しんだ 10進数で似たようなことをやってみると こうなります。 ここでは、10進数の世界では大抵割り切れない÷3を 10進数で有効桁数3で計算をしてみます。 ※計算している数値は違いますが、処理の内容は似た ようなことになります。 1 0.333 0.333 2 0.666 0.666 3 0.999 0.999 4 1.33 1.33 5 1.66 1.66 6 1.99 2.00 7 2.32 2.33 8 2.65 2.66 9 2.98 3.00 10 3.31 3.33 2番目の列が、1/3を 10進3桁で丸めた値 0.333 を 順次足したものです。 こちらが、ご質問の、最初の例に相当します。 あくまでも、「有効桁数」が固定なので、途中から、 小数点以下が2桁になってしまっています。 しかも、ここまで来ると、実質(0.333 ではなく)0.33 しか足すことができません。 3番目の列が、最初の数字を3で割って、3桁で丸めた ものです。 こちらがご質問の、2番目の例です。 こちらは、誤差の蓄積がないので、それぞれの数字は、 最初の数字を3で割ったもの(を丸めたもの)になって いると思います。 (これでも、丸めの誤差があるので、足し算をした結果 は誤差を含みます) それぞれの数字を足してゆくと、結果が異なってくるの がわかると思います。 誤差を少なくするためには、最初の列の数字をすべて足 したあとで、3で割るのがベストです。 これをご質問の例に適用すると、 1 + 2 + ... を計算した後で、計算結果を 10で割るのが 最も正確と言うことになります。 質問の例では、後者のほうが精度は良くなります。

noconan
質問者

お礼

返答が遅れてしまい、申し訳ありませんでした。 そして理解しやすい図まで提示してもらい感謝します。 しかし、分からない部分が一箇所。2列目は0.333自体をどんどん足していく処理で、3列目は1/3の結果を毎回足しています。 6行目で初めて差が出てくるのですが、どうしてこの6行目では0.001の差が出てくるのでしょう? 一応二つ共に0.333を途中まで足し、その後有効桁数の壁にかこまれ0.33の加算となっているので、差は出てこないように思われるのですが・・・・

すると、全ての回答が全文表示されます。
  • buriburi3
  • ベストアンサー率44% (353/792)
回答No.2

実数をどうやって表現しているのか?に起因する根源的な問題です。 通常は浮動小数点方式と言う指数部と実数部に分けて表現する方法をとりますが、この実数部の基底値が2であるため1/3や1/5,1/10等の2で重み付けできない値を扱うと丸め誤差が発生します。 丸め誤差の蓄積の差が計算結果の差になって現れます。 誤差を出したくないのであれば桁上げして(全体を10倍して)整数演算を行うのが簡単かと思います。(桁あふれに注意する必要がありますが)

noconan
質問者

お礼

返答遅れて申し訳ありませんでした。 浮動小数点演算や、固定小数点演算のことをすっかり忘れていました。 アホですね自分はw 解決策までアドバイスを頂ありがとうございました。 10倍してから計算をし、その後10で割れば一応誤差のない計算をすることができますね。

すると、全ての回答が全文表示されます。
  • Werner
  • ベストアンサー率53% (395/735)
回答No.1

> for(i=0.1, sum=0.0; i<=100.0; sum+=(float)i, i+=0.1); i+=0.1 で正確に0.1ずつ足すことはできないので誤差が出ます。 (なお、2つめの方法でもより小さいですが誤差は出ます。)  for(i=0.1, sum=0.0; i<=100.0; sum+=(float)i, i+=0.1)printf("%12.8f\n",i); のようにして確認してみては。

noconan
質問者

お礼

返答遅れて申し訳ありませんでした。 確かに自分が記述したソースよりも誤差が小さいですね。 アドバイスありがとうございました。

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

関連するQ&A