C言語:誤差について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
clr

C言語:誤差について

#1

投稿記事 by clr » 14年前

0<f<1を満たす10進数実数fを2進数小数(小数点は23桁まで)に変換するプログラムを書きたいのですが、
以下のコードを実行すると小数点16桁あたりで誤差が出てしまいます。
(0.10100010011100102325088_(2)と出力される)
どこで誤差が出ているのかよくわからないのですが、もしご存じの方がいたら教えてください
OSはmac10.6でコンパイラはxcodeのgccです

コード:

#include<stdio.h>
#include<math.h>

main(){

	int n;
	double f,sum;
	f=0.634567;
	for(n=1;n<=23;n=n+1){
		f=f*2.0;
		if(f>=1.0){
			f-=1.0;
			sum += pow(10.0,-n);
		}
	}printf("%.23f_(2)",sum);
}

box
記事: 2002
登録日時: 14年前

Re: C言語:誤差について

#2

投稿記事 by box » 14年前

誤差について論じる前の根本的な話として、sumを初期化しておかなくっていいのかなぁ、なんて思ったりしてます。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前
住所: 東海地方
連絡を取る:

Re: C言語:誤差について

#3

投稿記事 by softya(ソフト屋) » 14年前

double型の10進精度は約15桁です。
http://www.cc.kyoto-su.ac.jp/~yamada/pr ... tml#double
なので、小数点16桁以下は誤差になってしまいます。

gccでlong doubleが使えるなら必要な精度が出るはずです(速度は保証しません)。
あとsumは初期化したほうが良いでしょう。

コード:

#include<stdio.h>
#include<math.h>
 
main(){
 
    int n;
    long double f,sum=0;
    f=0.634567;
    for(n=1;n<=23;n=n+1){
        f=f*2.0;
        if(f>=1.0){
            f-=1.0;
            sum += pow(10.0,-n);
        }
    }
    printf("%.23Lf_(2)\n",sum);
}
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

box
記事: 2002
登録日時: 14年前

Re: C言語:誤差について

#4

投稿記事 by box » 14年前

softya(ソフト屋) さんが書きました: あとsumは初期化したほうが良いでしょう。
そういうレベルでいいんですか?本当に?
must initialize
じゃないんですか?
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

box
記事: 2002
登録日時: 14年前

Re: C言語:誤差について

#5

投稿記事 by box » 14年前

clr さんが書きました: 0<f<1を満たす10進数実数f
fが、
0.5
0.25
0.125
0.0625
0.03125
...
以下、2でずっと割っていく

の和(ああ、もちろん、使わない項もありますよ)として正確に表せるなら、誤差は出ないかもしれませんし、それでも誤差は出るかもしれません。
0.634567
がこれに該当するかどうかは怪しいですね。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

clr

Re: C言語:誤差について

#6

投稿記事 by clr » 14年前

お二方返信ありがとうございます。
sumの初期化は忘れてました。以後しておくようにします。
box さんが書きました: fが、
0.5
0.25
0.125
0.0625
0.03125
...
以下、2でずっと割っていく

の和として正確に表せるなら、誤差は出ないかもしれませんし、それでも誤差は出るかもしれません。
0.634567
がこれに該当するかどうかは怪しいですね。
誤差が出るとまずいというよりは、0.634567を2進数において
小数点23桁までは正確な値を示しておきたいという感じです
タイトル紛らわしかったですね…。すみません
softya(ソフト屋) さんが書きました:double型の10進精度は約15桁です。
http://www.cc.kyoto-su.ac.jp/~yamada/pr ... tml#double
なので、小数点16桁以下は誤差になってしまいます。

gccでlong doubleが使えるなら必要な精度が出るはずです(速度は保証しません)。
あとsumは初期化したほうが良いでしょう。

コード:

#include<stdio.h>
#include<math.h>
 
main(){
 
    int n;
    long double f,sum=0;
    f=0.634567;
    for(n=1;n<=23;n=n+1){
        f=f*2.0;
        if(f>=1.0){
            f-=1.0;
            sum += pow(10.0,-n);
        }
    }
    printf("%.23Lf_(2)\n",sum);
}
doubleが10進精度で15桁までは初耳でした。
softyaさんのコードも実行したのですが
0.10100010011100101669146_(2)と表示され、まだ正確な値が出ないようです
色々調べてみましたが、http://oshiete.goo.ne.jp/qa/145087.htmlによると
long double型に変えても精度はまちまちらしく、23桁まで精度があるとは限らないみたいなので、
その点に引っかかったのかもしれません

アバター
GRAM
記事: 164
登録日時: 14年前
住所: 大阪

Re: C言語:誤差について

#7

投稿記事 by GRAM » 14年前

いくつか混乱を招くような表現がありますので、(自分の理解があっているかどうかはわかりませんが)
問題と解決策を提示したいと思います。

まず23桁というのは2進数での小数点以下の桁ですね
long doubleを使う等のはなしがありましたが、double(floatでさえも)通常23bit以上の仮数部の精度がありますから
どうでもいいことだと思います。特にdoubleを使えば四捨五入の心配すらなくなることでしょう

それから0.634567が二進表記可能かどうかもまた同様にどうでもいいことです。
双方とも精度には全く関係ありません

さてでは何が問題なのか?
ずばりsum += pow(10.0,-n);こいつです
sumの初期化云々はおいておいて、sumが浮動小数点数であることが問題です。
欲しいのは2進のビット列であり、10.0-nという二進表記不可能な値を足し続ける意味がありません
ご存知かもしれませんが、0.1などという数は2進では表せません。生じた計算誤差は当然より小さな桁に蓄積されていくことになるでしょう
それが16桁以下の誤差につながりうるというわけです

具体的な解決策としては、整数型とビット演算を使って直接0または1を求めていくという方法です

コード:

#include<stdio.h>
#include<math.h>
 
int main(){
 
    int n;
    double f = 0.634567;
    unsigned int sum = 0;
    for(n=0;n<23;n=n+1){
        f=f*2.0;
        if(f>=1.0){
            f-=1.0;
            sum |= 1 << n;
        }
    }
    printf("0.");
    for(n=0;n<23;n=n+1){
            if(sum & 1 << n)printf("1");
            else printf("0");
    }
    printf("_(2)");
}  
としましょう。
これで出力は
   0.10100010011100101111101_(2)となり
真値0.101000100111001011111011101000000001111011101110・・・と完全に一致します^^;

追記:誤字が多いので直しました・・・orz
最後に編集したユーザー GRAM on 2011年5月10日(火) 00:15 [ 編集 1 回目 ]

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前
住所: 東海地方
連絡を取る:

Re: C言語:誤差について

#8

投稿記事 by softya(ソフト屋) » 14年前

失礼しました。コードを意味を良く見てませんでした(^^;
2進変換としては、誤差だらけですね。
根本的には、浮動小数点に頼らず計算したほうが良いと思います。

>>dicさん
もちろん初期化しないと数値はおかしくなりますよ。

【追記】
GRAMさん、ありがとうございます。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

clr

Re: C言語:誤差について

#9

投稿記事 by clr » 14年前

>>GRAMさん
返信ありがとうございます。
なるほど…。doubleでも23bit以上の仮数部の精度があるのですね。
ビット演算は実はまだ勉強していなくて、GRAMさんのコードを見てよくわからない所もあるのですが
このコードだと正確な値が求めれそうなので、詳しくビット演算を勉強してみることにします
ありがとうございました。

clr

Re: C言語:誤差について

#10

投稿記事 by clr » 14年前

>>softyaさん
>>根本的には、浮動小数点に頼らず計算したほうが良いと思います。

確かに…
とりあえず教えてもらったビット演算を使って求めてみることにします

閉鎖

“C言語何でも質問掲示板” へ戻る