問題描述
我一直認為隨機數(shù)會介于 0 和 1 之間,沒有 1
,即它們是來自半開區(qū)間 [0,1) 的數(shù)字.std::generate_canonical
的 cppreference.com 上的文檔 證實了這一點.
I always thought random numbers would lie between zero and one, without 1
, i.e. they are numbers from the half-open interval [0,1). The documention on cppreference.com of std::generate_canonical
confirms this.
但是,當我運行以下程序時:
However, when I run the following program:
#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!
";
}
return 0;
}
它給了我以下輸出:
Bug!
即它為我生成了一個完美的 1
,這會導致我的 MC 集成出現(xiàn)問題.這是有效的行為還是我這邊有錯誤?這給出了與 G++ 4.7.3 相同的輸出
i.e. it generates me a perfect 1
, which causes problems in my MC integration. Is that valid behavior or is there an error on my side? This gives the same output with 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
?
If this is correct behavior, how can I avoid 1
?
編輯 1:來自 git 的 G++ 似乎遇到了同樣的問題.我在
Edit 1: G++ from git seems to suffer from the same problem. I am on
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
產(chǎn)生
and compiling with ~/temp/prefix/bin/c++ -std=c++11 -Wl,-rpath,/home/cschwan/temp/prefix/lib64 test.c && ./a.out
gives the same output, ldd
yields
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
Edit 2: I reported the behavior here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176
編輯 3:clang 團隊似乎意識到了這個問題:http://llvm.org/bugs/show_bug.cgi?id=18767
Edit 3: The clang team seems to be aware of the problem: http://llvm.org/bugs/show_bug.cgi?id=18767
推薦答案
問題出在從 std::mt19937
(std::uint_fast32_t
) 的 codomain 映射浮動
;如果當前的 IEEE754 舍入模式不是舍入到負無窮大(注意默認值是舍入),那么在發(fā)生精度損失時,標準描述的算法會給出不正確的結果(與其對算法輸出的描述不一致)-到最近).
The problem is in mapping from the codomain of std::mt19937
(std::uint_fast32_t
) to float
; the algorithm described by the standard gives incorrect results (inconsistent with its description of the output of the algorithm) when loss of precision occurs if the current IEEE754 rounding mode is anything other than round-to-negative-infinity (note that the default is round-to-nearest).
帶有種子的 mt19937 的第 7549723 次輸出是 4294967257 (0xffffffd9u
),當四舍五入為 32 位浮點數(shù)時給出 0x1p+32
,它等于最大值mt19937, 4294967295 (0xffffffffu
) 的值,同時四舍五入為 32 位浮點數(shù).
The 7549723rd output of mt19937 with your seed is 4294967257 (0xffffffd9u
), which when rounded to 32-bit float gives 0x1p+32
, which is equal to the max value of mt19937, 4294967295 (0xffffffffu
) when that is also rounded to 32-bit float.
如果要指定從 URNG 的輸出轉(zhuǎn)換為 generate_canonical
的 RealType
時,標準可以確保正確的行為,四舍五入將向負數(shù)執(zhí)行無限;在這種情況下,這將給出正確的結果.作為 QOI,libstdc++ 做出這個改變會很好.
The standard could ensure correct behavior if it were to specify that when converting from the output of the URNG to the RealType
of generate_canonical
, rounding is to be performed towards negative infinity; this would give a correct result in this case. As QOI, it would be good for libstdc++ to make this change.
隨著這個變化,1.0
將不再生成;取而代之的是 0 < 的邊界值
將更頻繁地生成(每個 0x1.fffffep-N
N <= 8N
大約 2^(8 - N - 32)
,具體取決于 MT19937 的實際分布).
With this change, 1.0
will no longer be generated; instead the boundary values 0x1.fffffep-N
for 0 < N <= 8
will be generated more often (approximately 2^(8 - N - 32)
per N
, depending on the actual distribution of MT19937).
我建議不要直接將 float
與 std::generate_canonical
一起使用;而是在 double
中生成數(shù)字,然后向負無窮大舍入:
I would recommend to not use float
with std::generate_canonical
directly; rather generate the number in double
and then round towards negative infinity:
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
也會出現(xiàn)這個問題;解決方案是相同的,在 double
上專門化分布,并將結果向 float
中的負無窮大舍入.
This problem can also occur with std::uniform_real_distribution<float>
; the solution is the same, to specialize the distribution on double
and round the result towards negative infinity in float
.
這篇關于1.0 是 std::generate_canonical 的有效輸出嗎?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網(wǎng)!