ページ 1 / 1
C言語:誤差について
Posted: 2011年5月09日(月) 22:30
by clr
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);
}
Re: C言語:誤差について
Posted: 2011年5月09日(月) 22:42
by box
誤差について論じる前の根本的な話として、sumを初期化しておかなくっていいのかなぁ、なんて思ったりしてます。
Re: C言語:誤差について
Posted: 2011年5月09日(月) 23:10
by 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);
}
Re: C言語:誤差について
Posted: 2011年5月09日(月) 23:13
by box
softya(ソフト屋) さんが書きました:
あとsumは初期化したほうが良いでしょう。
そういうレベルでいいんですか?本当に?
must initialize
じゃないんですか?
Re: C言語:誤差について
Posted: 2011年5月09日(月) 23:26
by box
clr さんが書きました:
0<f<1を満たす10進数実数f
fが、
0.5
0.25
0.125
0.0625
0.03125
...
以下、2でずっと割っていく
の和(ああ、もちろん、使わない項もありますよ)として正確に表せるなら、誤差は出ないかもしれませんし、それでも誤差は出るかもしれません。
0.634567
がこれに該当するかどうかは怪しいですね。
Re: C言語:誤差について
Posted: 2011年5月10日(火) 00:00
by clr
お二方返信ありがとうございます。
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桁まで精度があるとは限らないみたいなので、
その点に引っかかったのかもしれません
Re: C言語:誤差について
Posted: 2011年5月10日(火) 00:13
by GRAM
いくつか混乱を招くような表現がありますので、(自分の理解があっているかどうかはわかりませんが)
問題と解決策を提示したいと思います。
まず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
Re: C言語:誤差について
Posted: 2011年5月10日(火) 00:14
by softya(ソフト屋)
失礼しました。コードを意味を良く見てませんでした(^^;
2進変換としては、誤差だらけですね。
根本的には、浮動小数点に頼らず計算したほうが良いと思います。
>>dicさん
もちろん初期化しないと数値はおかしくなりますよ。
【追記】
GRAMさん、ありがとうございます。
Re: C言語:誤差について
Posted: 2011年5月10日(火) 09:27
by clr
>>GRAMさん
返信ありがとうございます。
なるほど…。doubleでも23bit以上の仮数部の精度があるのですね。
ビット演算は実はまだ勉強していなくて、GRAMさんのコードを見てよくわからない所もあるのですが
このコードだと正確な値が求めれそうなので、詳しくビット演算を勉強してみることにします
ありがとうございました。
Re: C言語:誤差について
Posted: 2011年5月10日(火) 09:33
by clr
>>softyaさん
>>根本的には、浮動小数点に頼らず計算したほうが良いと思います。
確かに…
とりあえず教えてもらったビット演算を使って求めてみることにします