在C++中重新启动随机数序列

Restarting a random number sequence in C++

本文关键字:随机数序列 重新启动 C++      更新时间:2023-10-16

简短问题:如何在不干扰已经创建的随机序列的情况下生成随机数?或者,如何在编译时未知的位置重新启动随机数序列?

长问题:我有一个使用随机数序列的程序,在测试过程中总是使用相同的随机种子(在这种情况下为1)。然而,随着类的更改,它们可能会调用该随机序列,从而产生一个问题,即由于这些更改的类已经推进了序列,所以主中的随机数不再相同。

下面是一个简短的片段,展示了潜在的现象。让我们假设foo已经被添加,或者以改变随机数序列的方式被更改。

#include<iostream>
void foo(void);
int main() {
srand(1);
std::cout << rand() << 't' << rand() << std::endl;
srand(1);
foo();
std::cout << rand() << 't' << rand() << std::endl;
return 0;
}
void foo(void) {
// Some other function/class member that uses rand( )
rand();
}

这是,至少在我的机器上,踢出以下值:

18467  41
6334   18467

选项一,实现起来几乎和显而易见的一样不切实际,就是简单地计算rand()在main中及其所有调用的函数/类中被调用的次数,然后重新设定种子,并使用伪循环将序列推进到正确的位置。不幸的是,如上所述,这并不实用,因此需要一种不同的方法。

我试图找到代码的第一个潜在解决方案是创建一个特定于该类或函数的局部rand()序列,并从中提取数字,保持原始序列不变。

我试图找到代码的第二个潜在解决方案是从某个任意位置重新启动该序列,但我既不知道如何获得该位置,也不知道如何在该位置重新启动。

那么,在不具体知道rand()被调用了多少次的情况下,我如何保存序列中的位置,然后在重新播种和/或对rand(()进行其他调用后重新启动该序列?或者,在不干扰已经在进行的rand()序列的情况下,在这些类/函数中生成随机数?

不要使用srand()/rand()srand()/rand()实际上是单个全局生成器。它没有提供读取其状态的方法,因此对使用它的类的交错调用只会相互干扰。

C++有一个更好的库<random>和许多生成器,您可以拥有多个未耦合的实例。如果你想让不同的类使用不同的生成器,这就是你的答案。

您应该将srand()/rand()视为C遗留。C++有一个更加成熟和复杂的随机数库。

std::default_random_engine通常是单元测试的好选择,因为序列的统计特征通常是非关键的。如果它不是加密的或非常精确的统计应用程序,它也可能适合您的程序代码。

如果您的应用程序是一个严肃的统计应用程序typedef,您可以选择生成器和(在相关情况下使用auto),以便在必要时更改方法。生成器都是鸭子型兼容的(现代OO方式!),所以应该很容易进行修改。

srand()/rand()都不允许您在设置后"检查"状态。

但是,如果您需要它,所有<random>生成器都允许您序列化和反序列化它们的内部状态。

因此,如果你想让代码的某个部分重复上一个序列,你可以在一次运行中转储状态,然后在解决问题时破解代码以强制执行状态。

https://en.cppreference.com/w/cpp/numeric/random

如果您有一些主代码和一些测试脚本,那么在主代码中使用与测试脚本不同的生成器实例。考虑对不同的测试块使用不同的生成器。

如果你想在失败的测试之前插入一个测试来"探索"一个问题(常见做法),请在插入的代码中使用不同的生成器实例——至少在最初是这样。

当你发现一个错误时,最好的做法是尝试并设计一个非随机测试,无论未来的变化如何,都会暴露这个错误。除非在主代码中公开API,让测试脚本"强制"生成器的状态,否则这可能很困难。

有些人不喜欢API,它的存在只是为了允许测试工具,而只是加密应用程序中的"从不做"实践。

全局状态通常是糟糕的设计。忘记了C++,C应该一直提供(类似):

void srand_state(unsigned seed, RAND_STATE* state);
int rand_state(RAND_STATE* state);

其中CCD_ 14是实现定义的。然后指定RAND_STATE是一般可复制的。因此:

RAND_STATE saved;
memcpy(&saved,state,sizeof(RAND_STATE));

该实现保证以任何顺序对CCD_ 16或CCD_。

C-PRNG有点被忽视了。有些旧的实现比无用的更糟糕。

您可以使用<random>标头中的C++随机数生成器生成独立于rand()的随机数。每个生成器都有自己的状态,独立于rand()和其他生成器。您还需要一个分发对象来生成:

#include <iostream>
#include <random>
int main() {
std::default_random_engine rng{1}; // or some other seed
std::default_random_engine rng2{rng}; // you can create a copy of the state
std::uniform_int_distribution<> dist{1, 100}; // range of numbers you want
// use the first engine
std::cout << dist(rng) << 't' << dist(rng) << std::endl;
// Since each engine has its own state, functions
// you call will not affect the sequence.
// use the second engine
std::cout << dist(rng2) << 't' << dist(rng2) << std::endl;
return 0;
}

在我的机器上,这会产生以下内容:

1       14
1       14

好吧,我知道这有点晚了(我的答案是在3年后给出的),但我认为它可能会帮助其他人进行搜索,因为我也有同样的问题。。。我用一个简单的解决方案。。。

所以你只需再次输入种子:

#include <iostream>
#include <random>
int main()
{
srand(123);
std::cout << rand() << std::endl;
std::cout << rand() << std::endl;
std::cout << rand() << std::endl;
srand(123);
std::cout << rand() << std::endl;
std::cout << rand() << std::endl;
std::cout << rand() << std::endl;
return 0;
}

它会显示这个

440
19053
23075
440
19053
23075