1
votes

Short question: How to generate random numbers without disturbing the random sequence already created? OR, How to restart a random number sequence at a position not known at compile time?

Long question: I have a program which uses a random number sequence, and for which the same random seed is always used during testing (1 in this case). However, as classes get changed, they may call that random sequence, creating an issue where the random numbers in the main are no longer the same due to the fact that the sequence has been advanced by those changed classes.

Here is a short snippet which shows the underlying phenomenon. Lets assume that foo has either been added, or changed in such a way that it alters the random number sequence.

#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();
}

Which is, at least on my machine, kicking out the following values:

18467  41
6334   18467

Option one, which is nearly as impractical to implement as it is obvious, is to simply go count/calculate the number of times rand( ) gets called in the main and all of its called functions/classes and then reseed and use a dummy loop to advance the sequence to the correct position. Unfortunately, this is not practical as mentioned, so a different method is required.

The first potential solution for which I attempted to find code, was to create a local rand( ) sequence specific to that class or function and pull numbers from that, leaving the original sequence untouched.

The second potential solution for which I attempted to find code, was to restart that sequence from some arbitrary position, but I know neither how to get that position nor how to restart it at that position.

So then, without specifically knowing how many times rand( ) has been called, how can I save the position in the sequence and then restart that sequence after reseeding and/or other calls to rand( ) may have taken place? OR, alternatively, generate random numbers in those classes/functions without disturbing the already in progress rand( ) sequence?

3
C++ has better random number generation facilities through <random>. See en.cppreference.com/w/cpp/numeric/random. - ralismark
This does not answer your question on restarting a generator from a known state, but... Before calling rand reseed the generator deterministically. For Test 1, reseed with 1; Test 2, reseed with 2; and so on. That will allow you to recreate results and add arbitrary tests. - jww

3 Answers

3
votes

Do not use srand()/rand(). srand()/rand() are in effect a single global generator. It doesn't offer a way to read its state so interleaved calls to classes using it will just interfere with each other.

C++ has a better library <random> and a number of generators of which you can have multiple uncoupled instances. That's your answer if you want different classes to use different generators.

You should regard srand()/rand() as C legacy. C++ has a more mature and sophisticated random number library.

std::default_random_engine is usually a good choice for unit tests because the statistical features of the sequence are usually non-critical. It may also be fit for you program code too if it's not cryptographic or a very precise statistical application.

If you're application is a serious statistical application typedef your choice of generator and (where relevant use auto) to so you can vary the method if necessary. The generators are all duck-type compatible (the modern OO way!) so it should be easy to chop and change.

Neither srand()/rand() don't let you 'inspect' the state once set.

If you need it, however, all the <random> generators allow you to serialize and de-serialize their internal states.

So if you want some section of code to repeat a previous sequence you can dump out the state in one run, then hack the code to force the state while you resolve an issue.

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

If you've got some main code and some test scripts, definitely use a different generator instance(s) in the main code to the test scripts. Consider using different generators for different blocks of testing.

If you want to insert a test before a failing test to 'explore' an issue (common practice) use a different generator instance in the inserted code - at least initially.

Best practice when you've identified a bug is to try and engineer a non-randomised test that would expose the bug regardless of future changes. That can be hard unless you expose API in the main code that lets the test script 'force' the state of the generators.

Some people don't like API that exists only to allow test instrumentation but is only 'never do' practice in cryptographic applications.

Global state is generally bad design. Forgetting about C++, C should have always offered (something like):

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

where RAND_STATE is implementation defined. Then specify that RAND_STATE is trivially copy-able. So for:

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

the implementation guarantees that any number of future calls to rand(state) or rand(&saved) in any order will both return the same values when paired in order of times each has been called.

The C PRNG has been a bit of a neglected poor relation. Some old implementations were worse that useless.

1
votes

You can generate random number separate to rand() using C++ random number generators in the <random> header. Each generator has its own state independent from rand() and other generators. You also need a distribution object to generate:

#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;
}

On my machine, this produces this:

1       14
1       14
-1
votes

okay, I know that is a litle bit late (my answer is after 3 years from the question) but i thaught it may help other people that will search cas i have the same problem... and i solved it by a simble slution...

so u just enter the seed again... just it

#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;
}

and it will show this

440
19053
23075
440
19053
23075