x87系の浮動小数点の精度について
開発環境はC言語で,CentOS 64bit,gcc,Sandy Bridge世代のcore i7です.
Cで誤差の検証をしていて,倍精度を使って内積を計算していました.
計算した後,内積の結果aを,
printf("%.24e",a);
などとすると,24桁まで何か数字が入って出力されます.一応,いろんな値で試しました.
(それ以上何桁表示されるのかは未テスト,コンパイラの最適化のオプションなどで変化はない)
倍精度は15-16桁しか精度はないはずですが,残る8桁(24-16)として出力される値はなんなのでしょうか.
メモリ空間上にある後ろのゴミかと思って,aを配列で確保して,a[1]に0や値を入れたりしてみましたが,結果は変わらずです.
もしやメモリ空間上でも計算の中間結果の80bitとして値を確保しているのでしょうか?
もちろん,信頼区間は16桁なのでそれ以下は信頼しないべきしょうが,アーキテクチャの興味として,
・17桁目以降もあっている可能性があるのか?(一応,何らかの計算をして得た値なのか,ゴミなのか)
・17桁以降の値はなにか?メモリ空間として何ビット持っているのか?
が気になっています.
どなたかご教授ください.
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答4件
0
ベストアンサー
10進数に直したからだと思います。
2の52乗分の1は、
0.0000000000000002220446049250313080847263336181640625
なので、それくらいの桁にはなります。
1.0より最下位桁分だけ大きい数値を出力してみます。
lang
1#include <stdio.h> 2main(){ 3 double x; 4 char *p; 5 p=(char*)&x; 6 p[0]=1; // 最下位bitだけ1 7 p[1]=0; 8 p[2]=0; 9 p[3]=0; 10 p[4]=0; 11 p[5]=0; 12 p[6]=0b11110000; 13 p[7]=0b00111111; // 符号=0、指数=1023 14 printf("%.60e\n",x); 15} 16// => 1.000000000000000222044604925031308084726333618164062500000000e+00
投稿2015/07/03 12:01
編集2015/07/03 21:54総合スコア84380
0
直接的な回答ではありませんが、一応・・。
質問者さんは64bitのCentOSを使っているとおっしゃってますから、まずは本当にx87が使われているのか確認されたほうがいいと思います。x86_64(AMD64)と名乗るにはSSEが実装されてる必要があります。gccはx87の浮動小数点レジスタではなくSSEの浮動小数点レジスタを使うかもしれません。例えば私の64bitのLinux環境では何もオプションに指定しなければ内部精度128bitのxmmレジスタを使ったコードを出力しました。すべての浮動小数点演算をx87で行うには-mfpmath=387
オプションをつける必要があります。
そのうえでディスアセンブルして、メモリにストアするfstp命令を見つけてオペコードの先頭が0xddなのか0xdbなのかチェックして、どっちの精度で渡されてるのか確認します。それが0xddなら、「メモリ空間上でも計算の中間結果は80bitとして値を確保しているのでしょうか?」という質問には「私の環境ではNOです」と答えることができます。もし0xdbだったら・・そんなことは無いと願ってます。x86_64はアライメントに厳しいですし、C言語に80bit浮動小数点みたいな型は無いですから。
$ cat a.c #include <stdio.h> int main(int argc, char *argv[]) { double d = 0.111; printf("%.24e\n", d); return 0; } $ gcc -mfpmath=387 a.c $ ./a.out 1.110000000000000014432899e-01 $ objdump -D a.out | grep "<main>" -A 20 0000000000400506 <main>: 400506: 55 push %rbp 400507: 48 89 e5 mov %rsp,%rbp 40050a: 48 83 ec 30 sub $0x30,%rsp 40050e: 89 7d ec mov %edi,-0x14(%rbp) 400511: 48 89 75 e0 mov %rsi,-0x20(%rbp) 400515: dd 05 c5 00 00 00 fldl 0xc5(%rip) # 4005e0 <_IO_stdin_used+0x10> 40051b: dd 5d f8 fstpl -0x8(%rbp) 40051e: 48 8b 45 f8 mov -0x8(%rbp),%rax 400522: 48 89 45 d8 mov %rax,-0x28(%rbp) 400526: f2 0f 10 45 d8 movsd -0x28(%rbp),%xmm0 40052b: bf d8 05 40 00 mov $0x4005d8,%edi 400530: b8 01 00 00 00 mov $0x1,%eax 400535: e8 a6 fe ff ff callq 4003e0 <printf@plt> 40053a: b8 00 00 00 00 mov $0x0,%eax 40053f: c9 leaveq 400540: c3 retq
その他の2つの質問についてはotnさんが回答してくださってます。
投稿2015/07/04 13:28
総合スコア1149
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
・17桁目以降もあっている可能性があるのか?(一応,何らかの計算をして得た値なのか,ゴミなのか)
・17桁以降の値はなにか?
コンピュータ内部は、double型(2進表現)で値を保持してます。
んで、printf文で出力しているのは、これを10進表現した値です。
doubleの仮数部で表現可能な10進表記の範囲は限られるので、
有効桁以降は精度不足となり、値を保障出来ません。
なので、17桁以降でも有効桁付近であれば、足りない精度で頑張って
10進表現した結果、たまたま合っている可能性も一応あります。
・メモリ空間として何ビット持っているのか?
sizeof(double)でバイトサイズを求めれば、確認出来るかと。
※long doubleでなく通常のdoubleなら普通は64bitですね。
投稿2015/07/04 12:21
総合スコア490
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
x87 の 倍精度浮動小数点は、10バイトダブルと言われる特殊なやつじゃなかったかな。
long double 使ってるのでしょ?
https://en.wikipedia.org/wiki/Long_double
普通の 64bit のダブルは、有効桁数 14-15 桁程度、小数点以下だけなら結構表わせたはず。
long double は16 * 2 の専用バンクレジスタ使ったどーたらこーたらって記憶があるな。
8 * 4 だったかも。ちょい忘れました。確か、3*3の行列を1クロックで実行できるように
複数バンクの同時実行機能を備えていたような気がします。
10バイトダブルは、パック10進数(1バイトで2桁表現する)ですね。なので、有効桁数は確か 18桁くらいじゃなかったかと。
80ビット(10バイト)は、バンクレジスタに格納されています。CPUの汎用レジスタには10バイト
を格納するレジスタはないので、
足し算するときは、
1+2
は、
バンク0-0に1の80bit 表現値、
バンク0-1に2の80bit 表現値、
CoPro の専用加算命令で加算結果を、メモリに直接ロードしてたようなかすかな記憶があります。
そういうのを解説したソフトバンク製の日本語の資料を持っていたんですが、知人にあげちゃいまして・・・
投稿2015/07/03 18:48
総合スコア1693
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。