web-dev-qa-db-ja.com

long double(GCC固有)および__float128

GCC/x86でのlong double__float128の詳細情報を探しています(実際の問​​題よりも、好奇心から)。

おそらくこれを必要とする人はほとんどいないでしょう(私は初めて、本当にdoubleが必要でした)が、それでも価値があると思います(そして興味深い)ツールボックスに何があるか、そしてそれが何であるかを知ること。

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

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

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

背景(これはTL; DR部分です):
long doubleDBL_MAXを検索していたため、最初に<float.h>を見つけましたが、偶然にLDBL_MAXが次の行にあります。 「ああ、GCCには実際には128ビットの倍精度があり、それが必要というわけではありませんが、...かっこいい」と最初に考えました。驚き、驚き:sizeof(long double)は12を返します...待って、16を意味しますか?

CおよびC++標準は、当然のことながら、型の非常に具体的な定義を提供していません。 C99(6.2.5 10)は、doubleの数はlong doubleのサブセットであると述べていますが、C++ 03は(3.9.1 8)に、long doubleは少なくともdouble(同じことですが、言い方が異なるだけです)と同じ精度であると述べています)。基本的に、longintshortと同じように、標準はすべてを実装に任せます。

ウィキペディアによると、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
                  ^^       ^^^^^^^^

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

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

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

さらに、LDBL_MAXは、+inflong doubleの両方で__float128として機能するようです。 1.0E100または1.0E2000のような数値をLDBL_MAXに追加したり、__​​ some_code__から減算したりすると、同じビットパターンになります。
これまでは、foo_MAX定数はではない最大の表現可能な数を保持する必要があると信じていました(== --- ==)not+inf(どうやらそうではありません)そうではありませんか?)また、80ビットの数値が128ビットの値に対して+infとしてどのように機能する可能性があるのか​​もよくわかりません...多分、結局のところ、私は疲れすぎて何か間違ったことをしました。

28
Damon

広告1。

これらのタイプは、ダイナミックレンジが非常に大きい数値で機能するように設計されています。 long doubleは、x87 FPUにネイティブな方法で実装されています。ハードウェアで計算を行うハードウェアがないため、128b doubleは、現代のx86のソフトウェアモードで実装されると思われます。

面白いことに、多くの浮動小数点演算を連続して行うことは一般的であり、中間結果は実際には宣言された変数に格納されるのではなく、完全な精度を利用してFPUレジスタに格納されます。それが比較の理由です:

double x = sin(0); if (x == sin(0)) printf("Equal!");

安全ではなく、動作を保証できません(追加のスイッチがない場合)。

広告。 3。

使用する精度によって速度に影響があります。次のコマンドを使用して、FPUの精度を変更できます。

void 
set_fpu (unsigned int mode)
{
  asm ("fldcw %0" : : "m" (*&mode));
}

変数が短いほど速くなり、長いほど遅くなります。 128ビットの倍精度化はおそらくソフトウェアで行われるため、かなり遅くなります。

RAMメモリが無駄になるだけでなく、キャッシュが無駄になることも問題です。64ビットのダブルから80ビットのダブルに移動すると、メモリの33%(32b)からほぼ50%(64b)に無駄になります(キャッシュを含む)。

広告4。

一方、SSEには「拡張精度」などがないため、long double型は-mfpmath = sseと相互に排他的であることを理解しています。一方、__ float128は、SSE mathで完全に正常に機能するはずです(ただし、4倍精度の命令がない場合は、1:1命令ベースではありません)。これらの仮定に基づいていますか?

FPUとSSEユニットは完全に独立しています。SSEと同時にFPUを使用してコードを作成できます。SSEのみを使用するように制約するとコンパイラーは何を生成するのでしょうか?とにかくFPUを使用しようとしていますか?SSEを使用してプログラミングを行っており、GCCはそれ自体で単一のSISDのみを生成します。SIMDバージョンを使用するには、それを支援する必要があります。__float128はおそらく動作しますすべてのマシン、8ビットAVR uCでも、結局ビットをいじっているだけです。

16進数表現の80ビットは、実際には20桁の16進数です。おそらく、使用されていないビットは古い操作のものですか?私のマシンでは、コードをコンパイルし、ロングモードで20ビットのみ変更しました:66b4e0d2-ec09c1d5-00007ffe-deadbeef

128ビットバージョンでは、すべてのビットが変更されています。 objdumpを見ると、ソフトウェアエミュレーションを使用しているかのように見え、FPU命令はほとんどありません。

さらに、LDBL_MAXは、long doubleと__float128の両方で+ infとして機能するようです。 LDBL_MAXに1.0E100や1.0E2000のような数値を加算または減算すると、同じビットパターンになります。これまで、foo_MAX定数は+ infではない最大の表現可能な数を保持するべきだと私は信じていました(明らかにそうではありませんか?)。

これはおかしいようです...

また、80ビットの数値が128ビットの値に対して+ infとしてどのように機能する可能性があるのか​​もよくわかりません...多分、結局のところ、私は疲れすぎて何か間違ったことをしているのかもしれません。

おそらく拡張されています。 80ビットで+ infと認識されたパターンは、128ビットフロートでも+ infに変換されます。

21
Caladan

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

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

  2. まれなケースですが、80ビットの計算タイプを使用すると、他のタイプを直接使用する場合よりもわずかに正確でない結果が生じる可能性があります(他のタイプの計算で511/1024ulpのエラーが発生する場合、最悪の場合の丸めエラーは513/1024ulpです。 )、80ビットのタイプを使用した連鎖計算は、他のタイプを使用した計算よりも、しばしばmuchより正確-な場合があります。

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

ただし、浮動小数点演算へのこのアプローチを使用するには、計算で使用されるのと同じ精度で中間結果をコードに格納できるようにする必要があります。たとえば、_temp = d1+d2; d4=temp+d3;_は、 _d4=d1+d2+d3;_と同じ結果。私が知ることができることから、_long double_の目的はbeその型でした。残念ながら、K&Rはすべての浮動小数点値が同じように可変個メソッドに渡されるようにCを設計しましたが、ANSI Cはそれを破りました。元々設計されていたCでは、コードfloat v1,v2; ... printf("%12.6f", v1+v2);を指定すると、printfメソッドは_v1+v2_がfloatまたはdoubleを生成するかどうかを心配する必要がなくなります。結果が既知の値に強制されるためです。タイプに関係なく。さらに、_v1_または_v2_の型がdoubleに変更されたとしても、printfステートメントを変更する必要はありません。

ただし、ANSI Cでは、printfを呼び出すコードが、doubleと_long double_のどちらの引数であるかを認識している必要があります。 _long double_を使用するが、doubleと同義のプラットフォームで記述された多くのコード(過半数ではないにしても)は、_long double_値の正しい形式指定子を使用できません。 _long double_を可変長メソッド引数として渡される場合を除いて80ビット型にするのではなく、64ビットに強制変換されるため、多くのコンパイラは_long double_をdoubleと同義にし、中間計算の結果を保存する手段を提供します。計算に拡張精度型を使用するのは、その型がプログラマーに利用可能になっている場合にのみ有効であるため、ANSI Cが可変引数をうまく処理できなかったために問題が生じたとしても、拡張精度を悪と見なすようになりました。

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

2
supercat

要約すると、4.9999999999999999999999と5.0の差になります。

  1. 範囲が主な違いですが、重要なのは精度です。
  2. これらのタイプのデータは、大圏の計算やGPSシステムで使用される可能性が高い座標数学で必要になります。
  3. 精度は通常の倍精度よりもはるかに優れているため、計算の精度を失うことなく、通常は18桁の有効数字を保持できます。
  4. 拡張精度では80ビット(主に数学プロセッサで使用されます)を使用しているので、128ビットの方がはるかに正確です。
1
R Telkman

C99およびC++ 11では、組み込み浮動小数点型のエイリアスである型float_tおよびdouble_tが追加されました。大まかに言えば、float_tfloat型の値の間で算術を実行した結果の型であり、double_tdouble

0
user3405743