web-dev-qa-db-ja.com

1.0はstd :: generate_canonicalからの有効な出力ですか?

乱数は0と1の間にあるといつも思っていました。1なし、つまり、半開区間[0、 1)。 cppreference.comのドキュメント of std::generate_canonicalはこれを確認します。

ただし、次のプログラムを実行すると:

#include <iostream>
#include <limits>
#include <random>

int main()
{
    std::mt19937 rng;

    std::seed_seq sequence{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    rng.seed(sequence);
    rng.discard(12 * 629143 + 6);

    float random = std::generate_canonical<float,
                   std::numeric_limits<float>::digits>(rng);

    if (random == 1.0f)
    {
        std::cout << "Bug!\n";
    }

    return 0;
}

次の出力が表示されます。

Bug!

つまり、完璧な1が生成され、MC統合で問題が発生します。それは有効な動作ですか、それとも私の側にエラーがありますか?これにより、G ++ 4.7.3で同じ出力が得られます。

g++ -std=c++11 test.c && ./a.out

およびclang 3.3

clang++ -stdlib=libc++ -std=c++11 test.c && ./a.out

これが正しい動作である場合、1を回避するにはどうすればよいですか?

Edit 1:gitのG ++は同じ問題に悩まされているようです。私はいる

commit baf369d7a57fb4d0d5897b02549c3517bb8800fd
Date:   Mon Sep 1 08:26:51 2014 +0000

~/temp/prefix/bin/c++ -std=c++11 -Wl,-rpath,/home/cschwan/temp/prefix/lib64 test.c && ./a.outでコンパイルすると、同じ出力が得られ、lddが生成されます

linux-vdso.so.1 (0x00007fff39d0d000)
libstdc++.so.6 => /home/cschwan/temp/prefix/lib64/libstdc++.so.6 (0x00007f123d785000)
libm.so.6 => /lib64/libm.so.6 (0x000000317ea00000)
libgcc_s.so.1 => /home/cschwan/temp/prefix/lib64/libgcc_s.so.1 (0x00007f123d54e000)
libc.so.6 => /lib64/libc.so.6 (0x000000317e600000)
/lib64/ld-linux-x86-64.so.2 (0x000000317e200000)

Edit 2:ここで動作を報告しました: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176

Edit 3:clangチームは問題を認識しているようです: http://llvm.org/bugs/show_bug.cgi? id = 18767

124
cschwan

問題は、コドメインの_std::mt19937_(_std::uint_fast32_t_)からfloat;へのマッピングにあります。現在のIEEE754丸めモードが負の無限大への丸め以外の場合に精度の損失が発生すると、標準で記述されたアルゴリズムは誤った結果を出します(アルゴリズムの出力の記述と一致しません)(デフォルトは丸めであることに注意してください) -直近)。

シードを使用したmt19937の7549723番目の出力は4294967257(_0xffffffd9u_)であり、32ビット浮動小数点数に丸めると_0x1p+32_になります。これはmt19937の最大値4294967295(_0xffffffffu_ )それも32ビット浮動小数点数に丸められます。

URNGの出力から_generate_canonical_のRealTypeに変換するときに、負の無限大に向かって丸めを実行するように指定する場合、標準は正しい動作を保証できます。この場合、これにより正しい結果が得られます。 QOIとして、libstdc ++がこの変更を行うことは良いことです。

この変更により、_1.0_は生成されなくなりました。代わりに、_0x1.fffffep-N_の境界値_0 < N <= 8_がより頻繁に生成されます(MT19937の実際の分布に応じて、Nあたりおよそ2^(8 - N - 32))。

floatを_std::generate_canonical_と直接使用しないことをお勧めします。むしろ、doubleで数値を生成し、負の無限大に向かって丸めます。

_    double rd = std::generate_canonical<double,
        std::numeric_limits<float>::digits>(rng);
    float rf = rd;
    if (rf > rd) {
      rf = std::nextafter(rf, -std::numeric_limits<float>::infinity());
    }
_

この問題は_std::uniform_real_distribution<float>_でも発生する可能性があります。解決策は同じです。doubleの分布を特化し、floatの結果を負の無限大に丸めます。

121
ecatmur

標準に従って、1.0 有効ではない。

C++ 11§26.5.7.2関数テンプレートgenerate_canonical

このセクション26.5.7.2で説明したテンプレートからインスタンス化された各関数は、指定された一様乱数ジェネレーターgの1つ以上の呼び出しの結果を、指定されたRealTypeのメンバーにマッピングします。 gによって生成されたものは均一に分布し、インスタンス化の結果はtj 、≤tj <1、以下で指定されるように、可能な限り均一に分散されます。

39
Yu Hao