36

long doubleGCC/x86 に関する詳細な情報を探しています__float128(実際の問題というよりは好奇心からです)。

おそらくこれらを必要とする人はほとんどいないでしょう (私は初めて本当にを必要としましたdouble)。

その観点から、私のややオープンな質問を許してください:

  1. これらのタイプの実装の根拠と意図された使用法を、相互に比較して誰かが説明できますか? たとえば、標準で型が許可されているため、それらは「恥ずかしい実装」であり、 とまったく同じ精度であるdouble場合、またはファーストクラスの型として意図されている場合に誰かが文句を言う可能性がありますか?
  2. または、誰かが共有するための優れた使用可能な Web リファレンスを持っていますか? Google で検索し"long double" site:gcc.gnu.org/onlinedocsても、本当に役立つ情報はあまり得られませんでした。
  3. 「倍精度が必要だと思うなら、おそらく浮動小数点を理解していない」という共通のマントラが当てはまらないと仮定します。燃え尽きました...パフォーマンスに大きな影響を与えることなく、ジャンプしたり、代わりにジャンプしたりできると期待するのは合理的ですか?floatlong double__float128double
  4. Intel CPU の「拡張精度」機能は、歴史的にメモリとレジスタ間で値が移動されたときに厄介な驚きの原因となってきました。実際に 96 ビットが格納されている場合、long double型はこの問題を解消するはずです。一方、SSEには「拡張精度」などがないため、long doubleタイプが と相互に排他的であることは理解しています。一方、SSE 数学では完全に正常に動作するはずです (ただし、4 倍精度命令がない場合は、1:1 命令ベースではありません)。私はこれらの仮定で正しいですか?-mfpmath=sse__float128

(3. と 4. は、プロファイリングと逆アセンブルに費やされたいくつかの作業でおそらく把握できますが、他の誰かが以前に同じ考えを持っていて、すでにその作業を行っている可能性があります。)

背景 (これは TL;DR の部分です):で調べていたので
最初につまずきました。偶然にも次の行にいます。「ああ、GCC には実際には 128 ビットの double があります。私がそれらを必要としているわけではありませんが、...クールだ」というのが私の最初の考えでした。驚き、驚き: 12 を返します... 待って、16 のことですか?long doubleDBL_MAX<float.h>LDBL_MAXsizeof(long double)

当然のことながら、C および C++ 標準では、型の具体的な定義が示されていません。C99 (6.2.5 10) は、 の数値doubleは のサブセットであると述べていますがlong double、C++03 は (3.9.1 8) とlong double少なくとも同じ精度を持っていますdouble(これは同じことですが、言葉遣いが異なるだけです)。long基本的には、 、int、と同様に、標準は実装にすべてを任せていますshort

ウィキペディアによると、GCC は「使用されている物理ストレージに関係なく、x86 プロセッサで 80 ビットの拡張精度」を使用しています。

GCC のドキュメントには、すべて同じページに、i386 ABI のために型のサイズが 96 ビットであると記載されていますが、どのオプションでも 80 ビットを超える精度は有効にされません (ハァッ? 何?)、Pentium 以降も同様です。プロセッサは、それらを 128 ビットの数値として整列させたいと考えています。これは 64 ビットでのデフォルトであり、32 ビットで手動で有効にすると、32 ビットのゼロ パディングになります。

テストを実行する時間:

#include <stdio.h>
#include <cfloat>

int main()
{
#ifdef  USE_FLOAT128
    typedef __float128  long_double_t;
#else
    typedef long double long_double_t;
#endif

long_double_t ld;

int* i = (int*) &ld;
i[0] = i[1] = i[2] = i[3] = 0xdeadbeef;

for(ld = 0.0000000000000001; ld < LDBL_MAX; ld *= 1.0000001)
    printf("%08x-%08x-%08x-%08x\r", i[0], i[1], i[2], i[3]);

return 0;
}

を使用すると、出力はlong double次のようになります。マークされた数字は一定で、数字が大きくなるにつれて他のすべての数字が最終的に変化します。

5636666b-c03ef3e0-00223fd8-deadbeef
                  ^^       ^^^^^^^^

これは、80 ビットの数値ではないことを示しています。80 ビットの数値には 18 桁の 16 進数があります。22 桁の 16 進数が変更されていることがわかります。これは、96 ビットの数値 (24 桁の 16 進数) のように見えます。また、触れられていないため、128ビットの数値ではありません。これは、 12を返す0xdeadbeefことと一致しています。sizeof

の出力は、__int128実際には 128 ビットの数値のように見えます。すべてのビットが最終的に反転します。

ドキュメントで示されているように、コンパイルは 32 ビットのゼロ パディングで 128 ビットに揃えられ-m128bit-long-doubleません。long doubleどちらも使用していません__int128が、実際には 128 ビットに合わせて、値をパディング0x7ffdd000(?!) しているようです。

さらに、は との両方でLDBL_MAX機能するようです。likeまたはto/fromの数値を加算または減算すると、同じビット パターンが生成されます。 これまで、定数は表現可能な最大数を保持するものではないというのが私の信念でした (明らかにそうではありませんか?)。また、80 ビットの数値が 128 ビットの値と同じように機能する可能性があるかどうかもよくわかりません。1 日の終わりに疲れすぎて、何か間違ったことをしただけかもしれません。+inflong double__float1281.0E1001.0E2000LDBL_MAX
foo_MAX +inf+inf

4

4 に答える 4

6

IEEE-754 は、効率的なデータ ストレージを目的として 32 および 64 の浮動小数点表現を定義し、効率的な計算を目的として 80 ビット表現を定義しました。その意図は、引数を 80 ビットの浮動小数点値に変換し、それらを加算し、結果を 64 ビットの浮動小数点型に戻すことによって、与えられfloat f1,f2; double d1,d2;たステートメント likeを実行することでした。d1=f1+f2+d2;これには、他の浮動小数点型で直接操作を実行する場合と比較して、次の 3 つの利点があります。

  1. 32 ビット型と 64 ビット型の間の変換には別のコードまたは回路が必要ですが、「加算」の実装、「乗算」の実装、「平方根」の実装が 1 つずつあれば十分です。等

  2. まれに、80 ビットの計算型を使用すると、他の型を直接使用するよりもわずかに精度の低い結果が得られることがあります (他の型の計算で 511/1024ulp の誤差が生じる場合、最悪の場合の丸め誤差は 513/1024ulp です)。 )、80 ビット型を使用した連鎖計算は、他の型を使用した計算よりも正確であることが多く、場合によってははるかに正確です。

  3. FPU を使用しないシステムでは、double計算を実行する前に a を指数と仮数に分離し、仮数を正規化し、仮数と指数を に変換するのにdouble多少時間がかかります。ある計算の結果が別の計算の入力として使用され、破棄される場合、アンパックされた 80 ビット型を使用すると、これらの手順を省略できます。

ただし、浮動小数点演算へのこのアプローチが有用であるためには、コードが計算で使用されるのと同じ精度で中間結果を格納できることが不可欠です。これtemp = d1+d2; d4=temp+d3;により、 と同じ結果が得られd4=d1+d2+d3;ます。私が知る限り、の目的はそのタイプlong doubleなることでした。残念ながら、K&R はすべての浮動小数点値が可変引数メソッドに同じ方法で渡されるように C を設計しましたが、ANSI C はそれを破りました。元々設計された C では、 code が与えられた場合、結果が既知の型に強制されるためfloat v1,v2; ... printf("%12.6f", v1+v2);、メソッドは aまたは aを生成するprintfかどうかを心配する必要はありません。また、型が変わったり、v1+v2floatdoublev1v2doubleprintfステートメントを変更する必要はありません。

ただし、ANSI C では、どの引数がで、printfどの引数が であるかを呼び出すコードが認識されている必要があります。多くのコード (大部分ではないにしても) を使用しているが、それが同義であるプラットフォームで書かれたコードの多くは、値に正しい書式指定子を使用できません。可変引数メソッドの引数として渡される場合を除いて 80 ビット型にするのではなく、64 ビットに強制することになるため、多くのコンパイラは をと同義にすることにしました。doublelong doublelong doubledoublelong doublelong doublelong doubledouble中間計算の結果を格納する手段を提供しません。計算に拡張精度型を使用することは、その型がプログラマーに利用可能になった場合にのみ有効であるため、ANSI C が可変引数を適切に処理できなかっただけで問題が生じたにもかかわらず、多くの人々は拡張精度を悪と見なすようになりました。

PS --引数を最も効率的にプロモートできる型として定義されたもあった場合、 の意図された目的long doubleは利益を得るでしょう。おそらく 48 ビット型の浮動小数点ユニットを持たない多くのマシンでは、最適なサイズは 32 ビット (32 ビット演算を直接実行する FPU を備えたマシン) から 80 (使用するマシン) までの範囲である可能性があります。 IEEE-754 によって想定された設計)。しかし、今では遅すぎます。long floatfloat

于 2015-06-05T19:23:30.393 に答える