C言語関係掲示板

過去ログ

No.994 小数同士の計算

[戻る] [ホームページ]
No.12979

小数同士の計算
投稿者---Ken(2004/02/27 01:03:27)


最近、C言語を勉強しはじめましたんですが、
解らない所があるので誰か教えて下さい。

float a,b;

for(;;){

if (キーの入力) b = .1f;
else (キーの入力) b = -.1f;

a += b;
}

この様なプログラムを作ったんですけど、
最初の内は,変数aにちゃんと値
(0.1 0.2 0.3 〜 2.0 2.1・・・)が入るんですが、
途中ぐらいから、変数aの値がおかしくなってくるんです
(2.5 2.6 2.7 2.7999 2.9999・・・ )と・・・
この様な事が起きないようにするにはどうしたらいいんでしょうか?
教えて下さい。


No.12981

Re:小数同士の計算
投稿者---たか(2004/02/27 12:27:06)


>この様なプログラムを作ったんですけど、
>最初の内は,変数aにちゃんと値
>(0.1 0.2 0.3 〜 2.0 2.1・・・)が入るんですが、
>途中ぐらいから、変数aの値がおかしくなってくるんです
>(2.5 2.6 2.7 2.7999 2.9999・・・ )と・・・
>この様な事が起きないようにするにはどうしたらいいんでしょうか?
>教えて下さい。

誤差が累積しているのだと思います。float型の代わりにdouble型を使い、
.1fを.1とすれば直ると思います。しかしこれもdouble型の精度により
誤差が出てくる場所があります。

No.13000

小数同士の計算
投稿者---Ken(2004/02/28 00:55:00)


返信ありがとうございました。

教えて頂いた通りやってみたら、LSIC86の方では
ちゃんと計算出来るようになりました。
でも、microsoft VC++6.0 で同じ事をやってみると
0.1を20〜30回足すと数値がずれてきてしまうみたいです。

これは、こーゆーものなんでしょうか?

No.13002

Re:小数同士の計算
投稿者---RAPT(2004/02/28 10:58:24)


>でも、microsoft VC++6.0 で同じ事をやってみると
>0.1を20〜30回足すと数値がずれてきてしまうみたいです。
>
>これは、こーゆーものなんでしょうか?
数値は、10進数で書いても、内部的に2進数として処理されます。
ところが、10進数の小数点以下の数値について、2進数では表しきれない
ものがあります。その部分で、情報が欠落してしまったりします。
その2進数をまた10進数として表現しなおしたとき、当然ながら、
  元の10進数の値≠一部情報欠落後の10進数の値
となります。これが、誤差となって現れてきます。

そのため、小数点以下の値を扱う時、常に計算誤差を念頭に入れて
おかなくてはなりません。

詳しくは、基本情報処理技術者のテキストでも読んで下さい。

No.13004

Re:小数同士の計算
投稿者---たか(2004/02/28 14:43:29)


0.1は二進数で表すと0.000110011001100・・・という循環小数になり、入れ物
の大きさが限られている二進表現では正確に表せません。金融界ではこの
ためBCD演算を使用して少なくとも十進数の世界の中では変な誤差が悪さ
をしないようにしています。COBOL等の言語が二進化十進数をサポートしま
すが、CやC++でもライブラリを使えばもちろん使用可能です。

問題は、0.1や0.1fが具体的にどれくらいの誤差を持っているかですが、
これはマシンや処理系によって異なり、ANSI規格では機械イプシロンと
いう言葉を持って表現しています。これは誤差というよりどれ位の数を
1にたせば結果が反映されるかの最小の数を意味します。これより小さな
数を1に足しても結果が1のままです。1以外の数の場合は相対誤差となり
ます。

それ以外に、0.1fは結構大きな誤差を持っていて、何回か足しただけで
端数がおかしくなってきます。doubleに対しても20〜30回足しただけで
同じような誤差が現れたとありますが、これはちょっと考えられません。
DBL_DIG >= 10だからです。足す数が0.1でなくて0.1fのままではないで
すか?もう一度お確かめ下さい。

次のプログラムは0.1fと0.1の二進表現と、小数点以下16桁まで表示した
時の実行結果の例です。当然この実行結果は処理系によって異なります。

EPSILON = 1.192092895507812e-07
0.1000000014901161
00001100110011001100110011010000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000
EPSILON = 2.220446049250313e-16
0.1
00001100110011001100110011001100110011001100110011001101000000000000000000000000
000000000000000000000000000000000000000000000000

#include <stdio.h>
#include <float.h>

void epsilon(double eps, double f);

int main(void)
{
  epsilon(FLT_EPSILON, 0.1f);
  epsilon(DBL_EPSILON, 0.1);

  return 0;
}

void epsilon(double eps, double f)
{
  char buf[128], *p = buf;
  int i;
  
  printf("EPSILON = %.16g\n", eps);
  sprintf(buf, "%.16g\n", f);
  printf(buf);

  for(i = 0; f >= 0 && i < 128; f *= 2, i++) {
    if (f >= 1) {
      *p++ = '1';
      f -= 1;
    } else
      *p++ = '0';
  }
  *p = '\0';
  puts(buf);
}


No.13007

Re:小数同士の計算
投稿者---たか(2004/02/28 14:53:17)


>for(i = 0; f >= 0 && i < 128; f *= 2, i++) {

う、これは

for(i = 0; f >= 0 && i < 127; f *= 2, i++) {

でした。

No.13008

Re:小数同士の計算
投稿者---たか(2004/02/28 15:07:03)


間違いないようにコメントしておきますが、0.1fや0.1lfが真の値0.1から
ずれているのは機械イプシロンのせいというより、主に最下数のビット
に丸め誤差が発生しているためです。

十進数で言えば四捨五入のような状態です。
(例)

π=3.1415926535・・・→3.14 丸め誤差 0.0015926535・・・・

詳しくは「UNIXワークステーションによる科学技術計算ハンドブック、
戸川隼人著、サイエンス社 ISBN4-7819-0868-3、P95 4.基礎知識」の章を
読んで下さい。