5

アンダーフローを防ぐために、ループ内のdouble型の結果にいくつかの定数を効率的に追加または乗算する必要があります。たとえば、intがある場合、コンパイラはビットシフトを使用するため、2の累乗で乗算すると高速になります。double効率的な足し算と掛け算のための定数の形式はありますか?

編集:私の質問を理解している人はあまりいないようです、私の怠惰についてお詫びします。コードを追加します。がintの場合a、これ(2の累乗を掛ける)はより効率的です

int a = 1;
for(...)
    for(...)
        a *= somefunction() * 1024;

1024をたとえば1023に置き換えた場合よりも。intに追加したい場合に最適なものはわかりませんが、それは私の興味ではありません。aダブルの場合に興味があります。効率的に加算してdoubleに乗算できる定数の形式(たとえば、2の累乗)は何ですか?定数は任意であり、アンダーフローを防ぐのに十分な大きさである必要があります。

これはおそらくCとC++だけに制限されているわけではありませんが、より適切なタグはわかりません。

4

6 に答える 6

4

最近のほとんどのプロセッサでは、単純に2の累乗で乗算する(たとえば、 2 10x *= 0x1p10;で乗算する、または2 10x *= 0x1p-10;で除算する)と、高速でエラーが発生しません(結果がオーバーフローするほど大きいか、アンダーフローするほど小さい場合を除く)。

一部の浮動小数点演算に対して「アーリーアウト」を備えたプロセッサがいくつかあります。つまり、特定のビットがゼロであるか、他の基準を満たしている場合、命令をより迅速に完了します。ただし、浮動小数点の加算、減算、および乗算は通常、約4 CPUサイクルで実行されるため、早期のアウトがなくてもかなり高速です。さらに、最近のほとんどのプロセッサは一度に複数の命令を実行するため、乗算の実行中に他の作業が進行し、パイプライン化されるため、通常、CPUサイクルごとに1つの乗算を開始(および終了)できます。(時々もっと。)

2の累乗を掛けても、仮数(値の小数部分)は変更されないため、丸め誤差はありません。したがって、新しい仮数は正確に表現できます。(ただし、1未満の値を乗算すると、仮数のビットが浮動小数点型の制限よりも低くなり、アンダーフローが発生する可能性があります。一般的なIEEE 754倍精度形式の場合、値が以下になるまでこれは発生しません。 0x1p-1022。)

スケーリング(または以前のスケーリングの効果を元に戻すため)に除算を使用しないでください。代わりに、逆数を掛けます。(以前の0x1p57のスケーリングを削除するには、0x1p-57を掛けます。)これは、ほとんどの最新のプロセッサでは除算命令が遅いためです。たとえば、30サイクルは珍しいことではありません。

于 2012-08-04T10:38:55.227 に答える
2

まず、ダブルを結合して、「範囲」「指数」の部分を選択します。次に、 「指数」または「範囲」の部分のみをシフトします。IEEE浮動小数点標準を探します。サインと最後の仮数ビットを忘れないでください。

union int_add_to_double
{
double this_is_your_double_precision_float;
struct your_bit_representation_of_double
    {
    int range_bit:53;//you can shift this to make range effect
    //i dont know which is mantissa bit. maybe it is first of range_bit. google it.
    int exponent_bit:10;   //exponential effect
    int sign_bit:1;     //take negative or positive
    }dont_forget_struct_name;
}and_a_union_name;
于 2012-08-04T06:50:16.010 に答える
2

最新のプロセッサでは、浮動小数点の加算と乗算は通常、数サイクルかかります。

おそらく、一歩下がって、アルゴリズムが何をしているのかを考える必要があります。あなたの例では、二重にネストされたループがあります...つまり、「somefunction()」が何度も呼び出される可能性があります。「double」の一般的な表現はIEEEであり、指数に11ビット、仮数に52ビットを使用します(ゼロを除いて暗黙の「1」があるため、実際には53ビットです)。つまり、非常に小さい数から非常に大きい数まで、53ビットの精度で数値を表すことができます。2進数の「浮動小数点」は、数値「1.0」の左または右に1024(2 ^ 10)桁移動できます。 ..「somefunction()」が1000回呼び出され、常に0.5以下の数値を返す場合、アンダーフローします(0.5を掛けるたびに、数値「a」を半分に減らします。つまり、バイナリ浮動小数点を左に移動します。x86では、制御レジスタにビットを設定することで、プロセッサに「非正規化数をゼロにフラッシュする」ように指示できます。これを行うためのポータブルプログラミングインターフェイスはありません。gccを使用します。

_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);

非正規化数をゼロにフラッシュするようにプロセッサに指示すると、プロセッサは法線(非正規化数または非正規化数)を超える(より小さい)数値を表現しようとしないため、コードの実行が速くなります。サブ法線(精度の低下を強制する)を生成しているアルゴリズムに直面して、精度を維持しようとしているようです。これをどのように処理するのが最適かは、「somefunction()」を制御するかどうかによって異なります。その関数を制御できる場合は、関数が返す値を範囲内の何かに「正規化」することができます。

0.5 <= X <= 2.0

つまり、戻り値は1.0を中心とし、適切にスケーリングするために最終的な答えを乗算する必要がある2の累乗とは別に追跡します。

于 2012-08-04T14:53:37.027 に答える
1

SSEを使用している場合、指数フィールドに定数を直接追加することは正当なトリックです(FPUコードでは非常にひどいです)-通常、スループットが2倍になり、レイテンシーが4倍向上します(float-> intを持つプロセッサーを除く)および/またはint->floatペナルティ)。しかし、非正規化数を防ぐためにこれを行っているだけなので、FTZ(ゼロにフラッシュ)とDAZ(非正規化数はゼロ)をオンにしてみませんか?

于 2012-08-04T15:12:08.803 に答える
0

IEE754値をコンポーネントに分割する標準のfrexp/ldexp関数を使用できます。

http://www.cplusplus.com/reference/clibrary/cmath/frexp/

http://www.cplusplus.com/reference/clibrary/cmath/ldexp/

簡単なサンプルコードは次のとおりです。

#include <cmath>
#include <iostream>

int main ()
{
  double value = 5.4321;
  int exponent;

  double significand = frexp (value , &exponent);
  double result = ldexp (significand , exponent+1);

  std::cout << value << " -> " << result  << "\n";
  return 0;
}

実行は以下を扱います:http://ideone.com/r3GBy

于 2012-08-04T06:54:40.043 に答える
0

ギガヘルツプロセッサでは、この方法(シフトと演算)を最適化することで、1または2ナノ秒を節約できます。ただし、メモリからのロードと保存にかかる時間は100ナノ秒のオーダーであり、ディスクへの時間は10ミリ秒です。キャッシュ使用量とディスクアクティビティを最適化する場合と比較して、算術演算について心配することは無意味です。実際の制作プログラムに違いはありません。

誤解を防ぐために、違いが小さいと言っているのではないので、心配しないでください。ゼロだと言っています。ALU時間の差が、CPUがメモリまたはI/Oの待機を停止した時間と完全に重ならない単純なプログラムを作成することはできません。

于 2012-08-04T14:03:31.123 に答える