1. ホーム
  2. c++

[解決済み] std::generate_canonicalの出力は1.0が有効か?

2022-08-18 14:04:44

質問

乱数は0と1の間にあるとばかり思っていたのですが、実際はどうなのでしょうか? がなく 1 すなわち、それらは半開放区間[0,1]からの数である。また のドキュメントを参照してください。 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 ?

編集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)

編集2 : こちらで挙動を報告しました。 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176

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

どのように解決するのですか?

この問題は std::mt19937 ( std::uint_fast32_t ) から float 現在の IEEE754 丸めモードが round-to-negative-infinity 以外の場合 (デフォルトは round-to-nearest であることに注意)、精度の損失が発生すると、この標準によって記述されたアルゴリズムは正しくない結果 (アルゴリズムの出力に関する記述と一致しない) を出力します。

mt19937の7549723番目の出力は、あなたのシードで4294967257です( 0xffffffd9u ) で、これを 32 ビット浮動小数点に丸めると 0x1p+32 となり、これは mt19937 の最大値 4294967295 に等しい ( 0xffffffffu ) に等しく、これも32ビット浮動小数点に丸められたときです。

規格は、URNG の出力から変換する際に RealTypegenerate_canonical の場合、負の無限大に丸めることになるので、この場合は正しい結果になります。 QOIとして、この変更を行うことはlibstdc++にとって良いことでしょう。

この変更で 1.0 は生成されなくなり、代わりに境界値 0x1.fffffep-N に対して 0 < N <= 8 はより頻繁に生成されます(約 2^(8 - N - 32) につき N MT19937の実際の分布に依存します)。

を使用しないことをお勧めします。 floatstd::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 .