1.0是std :: generate_canonical的有效输出吗?

csc*_*wan 124 c++ random c++11

我一直认为随机数在0和1之间,没有1,即它们是来自半开区间[0,1]的数字.cppreference.com上的文件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;
}
Run Code Online (Sandbox Code Playgroud)

它给了我以下输出:

Bug!
Run Code Online (Sandbox Code Playgroud)

即它1会让我产生完美,这会导致我的MC集成出现问题.是有效行为还是我身边有错误?这给出了与G ++ 4.7.3相同的输出

g++ -std=c++11 test.c && ./a.out
Run Code Online (Sandbox Code Playgroud)

和铿锵3.3

clang++ -stdlib=libc++ -std=c++11 test.c && ./a.out
Run Code Online (Sandbox Code Playgroud)

如果这是正确的行为,我该如何避免1

编辑1:来自git的G ++似乎遇到了同样的问题.我在

commit baf369d7a57fb4d0d5897b02549c3517bb8800fd
Date:   Mon Sep 1 08:26:51 2014 +0000
Run Code Online (Sandbox Code Playgroud)

和编译~/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)
Run Code Online (Sandbox Code Playgroud)

编辑2:我在这里报告了这种行为:https://gcc.gnu.org/bugzilla/show_bug.cgi?id = 63176

编辑3:clang团队似乎意识到了这个问题:http://llvm.org/bugs/show_bug.cgi?id = 18767

eca*_*mur 121

问题在于从std::mt19937(std::uint_fast32_t)的codomain映射到float; 如果当前IEEE754舍入模式不是圆到负无穷大,则标准描述的算法给出不正确的结果(与其对算法输出的描述不一致),如果出现精度损失(注意默认为圆形) -to-最近).

带有种子的mt19937的7549723rd输出是4294967257(0xffffffd9u),当舍入到32位浮点数时0x1p+32,它等于mt19937的最大值,4294967295(0xffffffffu),当它也被舍入到32位浮点数时.

该标准可以确保正确的行为,如果它是指定从革联的输出转换到时RealTypegenerate_canonical,舍入将被向负无穷执行; 在这种情况下,这将给出正确的结果.作为QOI,libstdc ++进行此更改会更好.

通过此更改,1.0将不再生成; 代替边界值0x1.fffffep-N0 < 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());
    }
Run Code Online (Sandbox Code Playgroud)

这个问题也可能发生std::uniform_real_distribution<float>; 解决方案是相同的,以专门化分布double和结果向负无穷大float.

  • @ecatmur建议; 更新这篇文章,提到`std :: uniform_real_distribution <float>`会因此而遭遇同样的问题.(因此搜索uniform_real_distribution的人会出现此Q/A). (3认同)
  • @user实现质量 - 使一个符合要求的实现比另一个更好的所有事情,例如性能,边缘情况下的行为,错误消息的有用性. (2认同)
  • @supercat:稍微离题,实际上有充分的理由试图使正弦函数尽可能准确地用于小角度,例如因为sin(x)中的小错误可能会变成sin(x)/ x中的大错误(其中当x接近于零时,[在实际计算中经常发生](http://en.wikipedia.org/wiki/Sinc_function).在π的倍数附近的"额外精度"通常只是其副作用. (2认同)
  • @ecatmur,我不确定您为什么要向负无穷大舍入。由于`generate_canonical` 应该生成一个范围为`[0,1)` 的数字,而且我们正在谈论它偶尔会生成1.0 的错误,那么向零舍入是否也同样有效? (2认同)

Yu *_*Hao 39

根据标准,1.0无效.

C++11§26.5.7.2函数模板generate_canonical

从本节中所描述26.5.7.2模板实例化的每个功能所提供的均匀的随机数发生器的一个或多个调用的结果映射g到指定的RealType的一个成员,使得,如果值克产生由g均匀地分布,则实例化的结果吨Ĵ,0≤吨Ĵ <1 ,分布尽可能均匀地如下规定.

  • +1我在OP的程序中看不到任何缺陷,所以我称之为libstdc ++和libc ++错误......这本身似乎不太可能,但我们去了. (25认同)