3

これはおそらく x86 FPU の専門家に対する質問です。

[最小、最大] の範囲でランダムな浮動小数点値を生成する関数を作成しようとしています。問題は、私の生成アルゴリズム (興味があるなら、浮動小数点 Mersenne Twister) が [1,2) の範囲の値しか返さないことです。つまり、包括的な上限が必要ですが、「ソース」で生成された値は排他的上限から。ここでの問題は、基になるジェネレーターが 8 バイトの double を返すことですが、必要なのは 4 バイトの float だけであり、デフォルトの FPU 丸めモードである Nearest を使用しています。

私が知りたいのは、この場合の切り捨て自体が、FPU 内部の 80 ビット値が十分に近い場合に戻り値が最大値を含む結果になるかどうか、または最大値に乗算する前に最大値の仮数をインクリメントする必要があるかどうかです。 [1,2) の中間ランダム、または FPU モードを変更する必要があるかどうか。もちろん、その他のアイデアも。

現在使用しているコードは次のとおりです。1.0f が 0x3f800000 に解決されることを確認しました。

float MersenneFloat( float min, float max )
{
    //genrand returns a double in [1,2)
    const float random = (float)genrand_close1_open2(); 
    //return in desired range
    return min + ( random - 1.0f ) * (max - min);
}

違いがある場合、これは Win32 MSVC++ と Linux gcc の両方で動作する必要があります。また、SSE 最適化のいずれかのバージョンを使用すると、これに対する答えが変わりますか?

編集:答えはイエスです。この場合、double から float への切り捨ては、結果に max を含めるのに十分です。詳細については、Crashworks の回答を参照してください。

4

3 に答える 3

4

SSE ops は、中間の 80 ビット表現を持たないため、このアルゴリズムの動作を微妙に変更します。計算は実際には 32 ビットまたは 64 ビットで行われます。幸いなことに、MSVC に /ARCH:SSE2 コマンド ライン オプションを指定するだけで、簡単にテストして結果が変わるかどうかを確認できます。これにより、通常の浮動小数点の x87 FPU 命令の代わりに SSE スカラー演算が使用されます。算数。

正確な丸め動作が整数境界の周りにあることはわかりませんが、1.999 .. が 64 ビットから 32 ビットに丸められるときに何が起こるかを確認するためにテストできます

static uint64 OnePointNineRepeating = 0x3FF FFFFF FFFF FFFF // exponent 0 (biased to 1023), all 1 bits in mantissa
double asDouble = *(double *)(&OnePointNineRepeating);
float asFloat = asDouble;
return asFloat;

編集、結果:元の投稿者がこのテストを実行したところ、切り捨てにより、1.99999 は /arch:SSE2 の有無にかかわらず 2 に切り上げられることがわかりました。

于 2009-03-13T22:03:39.127 に答える
0

範囲の両端を含むように丸めを調整した場合、それらの極端な値は、極端でない値の半分の可能性しかありませんか?

于 2009-03-13T21:35:29.553 に答える
0

切り捨てでは、最大値を含めることはできません。

本当に最大値が必要ですか? 正確に最大値に到達する可能性は、文字通りほぼ 0 です。

とはいえ、精度をあきらめているという事実を利用して、次のようなことを行うことができます。

float MersenneFloat( float min, float max )
{
    double random = 100000.0; // just a dummy value
    while ((float)random > 65535.0)
    {
        //genrand returns a double in [1,2)
        double random = genrand_close1_open2() - 1.0; // now it's [0,1)
        random *= 65536.0; // now it's [0,65536). We try again if it's > 65535.0
    }
    //return in desired range
    return min + float(random/65535.0) * (max - min);
}

MersenneFloat を呼び出すたびに、genrand が複数回呼び出される可能性がわずかにあることに注意してください。したがって、閉じた間隔で可能なパフォーマンスをあきらめました。double から float にダウンキャストしているため、精度を犠牲にすることはありません。

編集:改善されたアルゴリズム

于 2009-03-13T21:44:02.637 に答える