C++,某些组合从未使用rand()出现

C++, certain combos never appear using rand()

本文关键字:rand 出现 未使用 组合 C++      更新时间:2023-10-16

我对0和2之间的两个int使用rand()。看起来第一个int从来都不是0,而第二个int是2。这是我的测试代码-

#include <iostream>
#include <time.h>
int main()
{
    srand(time(NULL));
    int number1, number2;
    number1 = rand() % 3;
    number2 = rand() % 3;
    printf("%i, %i", number1, number2);
    return 0;
}

Output-25尝试

2, 2
2, 2
2, 1
1, 2
0, 1
2, 2
1, 2
1, 0
2, 1
1, 0
0, 0
1, 2
2, 2
0, 0
2, 1
1, 0
2, 2
1, 0
2, 1
1, 0
0, 1
1, 2
1, 0
0, 0
2, 2

正如你所看到的,在25次尝试中,组合从来都不是0,2。这是我可能应该转移到<随机>?此外,永远不会有2,0。

不,这将发生在所有种子的9*(1-1/9)^25=0.4736,或大约50%的时间。也就是说,在你运行程序的前25个结果中,大约有一半的时间会丢失一些数字在{0,1,2}中的两位数序列。

再运行一次,看看会发生什么。

  1. 您绝对应该使用<random>。你越早忘记rand的存在,你就会越快乐

  2. CCD_ 3有时用线性同余生成器(LCG)来实现。LCG存在许多缺陷,最相关的是顺序生成的数字的低阶比特高度相关。因此,您永远不应该使用rand() % k来生成[0, k)范围内的数字。还有其他原因。事实上,从有限的范围生成无偏随机整数涉及到一些微妙之处,<random>会为您处理这些问题。

  3. srand(time(NULL))将随机数生成器的种子设定为从epoch开始的当前时间(以秒为单位),这意味着如果您按顺序运行程序几次,种子将相同或相似。如果种子相同,则随机数序列也将相同。如果种子相似,则随机数序列也可以相似。所以,除了在长时间运行的程序中,不要这样做。为伪随机数生成器找到一个好的种子可能很棘手。<random>实现将具有更好的默认行为,因此您通常不需要担心这一点。

获取%3不仅仅取决于低阶位。

我使用VC++运行了下面的程序,模拟在调用之间的一秒钟内运行OP的程序1000万次。它没有显示出任何偏见。

start = 1413167398
(0, 0) 1110545
(0, 1) 1111285
(0, 2) 1111611
(1, 0) 1111317
(1, 1) 1111666
(1, 2) 1110451
(2, 0) 1111580
(2, 1) 1110491
(2, 2) 1111054
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <map>
#include <utility>
int main()
{
    std::map<std::pair<int, int>, int> counter;
    unsigned int start = static_cast<unsigned int>(std::time(nullptr));
    std::cout << "start = " << start << std::endl;
    unsigned int finish = start + 10000000;
    for (unsigned int seed = start; seed != finish; ++seed)
    {
        std::srand(seed);
        int x = rand() % 3;
        int y = rand() % 3;
        ++counter[std::make_pair(x, y)];
    }
    for (auto iter = counter.cbegin(); iter != counter.cend(); ++iter)
    {
        std::cout << "(" << iter->first.first << ", " << iter->first.second << ") ";
        std::cout << iter->second << std::endl;
    }
    return 0;
}

确实如此。这段代码在第一次运行时给了我0.2对:

for( int i = 0; i < 20; ++i) {
  number1 = rand() % 3;
  number2 = rand() % 3;
  printf("%i, %in", number1, number2);
}

从均匀分布中生成真正的随机数并不能保证给定的(可能的)数会出现在有限数量的试验中。良好PRNG的四个BSI标准中的K2是

K2——与"true"不可区分的数字序列随机数。

因此,生成伪随机数当然倾向于以与从真实随机分布中采样相同的方式表现——尽管由于限制,任何(可能的)数都会在某个点出现(在小于或等于其周期的时间)。

http://ideone.com/c5oRQL


使用std::uniform_int_distribution

除此之外,rand()并不是最好的发生器。每当模运算中的除数不能均匀地划分PRNG的范围时,它总是引入偏差。运算符%使得以这种方式产生的概率分布偏斜,因为作为rand()的最大值的RAND_MAX可以不等于k*3+2。如果除数不能均匀地划分范围,那么分布将是偏斜的,并且偏差随着除数的增加而增加。你可以在这里阅读更多关于这方面的内容。总结一下:在C++中,您应该使用<random>库:

#include <iostream>
#include <random>
int main()
{
    std::random_device rd;
    std::mt19937 gen( rd());
    std::uniform_int_distribution<> dis( 0, 2);
    for ( int n = 0; n < 25; ++n)
        std::cout << dis(gen) << ' ';
    std::cout << 'n';
}