乱数は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
問題は、コドメインの_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
の結果を負の無限大に丸めます。
標準に従って、1.0
有効ではない。
C++ 11§26.5.7.2関数テンプレートgenerate_canonical
このセクション26.5.7.2で説明したテンプレートからインスタンス化された各関数は、指定された一様乱数ジェネレーター
g
の1つ以上の呼び出しの結果を、指定されたRealTypeのメンバーにマッピングします。私g
によって生成されたものは均一に分布し、インスタンス化の結果はtj 、≤tj <1、以下で指定されるように、可能な限り均一に分散されます。