srand(time(NULL)) and rand()

Hello everyone. I am playing with random numbers, trying to understand how srand() and rand() work together in order to generate some randomized numbers. I understand that srand(time(NULL)) generates a seed according to a large amount of seconds since the first january 1970. However, if I display the seed and a randomized number between 0 and 99 in my console, I can see that this is not really a randomized number because of a clear repetitive sequence - a evidente incrementation. How works the rand() function according to its seed ?

PS : I know that I have not to call srand() because one is enough at the start, but I did that just for the exercise in order to check evolution. Thank you for your help ++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <Windows.h>

using namespace std;

int main()
{
    int s = 0;
    int ratio = 100;
    bool running = true;

    while(running)
    {
	if (GetKeyState(VK_SPACE) & 0x8000)
	    running = false;

	if (time(NULL) != s)
	{   // determines how many seconds since 1/1/1970
	    s = time(NULL);
	    cout << "Seconds since 1/1/1970 : " << s << endl;
	    // random number between 0 and ratio
	    srand(s);
	    int x = rand() % ratio;
	    cout << "Randomized number between 0 and " << ratio << " : " << x << endl;
	}
    }

    return 0;
}
Last edited on
Hum... it's more complicated than I thought. On my computer, I have some sequence like 22, 25, 28, 32, 35, 39, etc - like an incrementation by 3 or 4. However, using a compiler on-line, it works as expected generating some randomized numbers (but Windows.h is not embedded - you have to erase this part of code for keyboard events). What it means? My code has a totally different behavior on my PC using VisualStudio 2022. Strange :/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

using namespace std;

int main()
{
    int s = 0;
    int ratio = 100;
    bool running = true;

    while(running)
    {
        if (time(NULL) != s)
	{   // determines how many seconds since 1/1/1970
	    s = time(NULL);
	    cout << "Seconds since 1/1/1970 : " << s << endl;
	    // random number between 0 and ratio
	    srand(s);
	    int x = rand() % ratio;
	    cout << "Randomized number between 0 and " << ratio << " : " << x << endl;
	}
    }

    return 0;
}
Last edited on
There are known issues with the C library srand()/rand(). For C++, use <random>

Consider:

1
2
3
4
5
6
7
8
9
10
11
#include <random>
#include <iostream>

std::mt19937 rng(std::random_device {}());

int main() {
	const std::uniform_int_distribution<unsigned> distrib(0, 99);

	for (size_t i {}; i < 100; ++i)
		std::cout << distrib(rng) << ' ';
}


This will give different sequences every time the program is used.

See http://www.cplusplus.com/reference/random/
Last edited on
Thank you for your help. If it's a known issue, it is especially specific to Visual Studio. On my PC, it does not work, but it runs fine on other compilers. I will keep in mind that your method with random works better. Thank you for your help and have a nice day ++
Last edited on
srand() sets the RNG to a particular starting point in a sequence. Calling srand() repeatedly within the same second can cause the RNG to return the same series of pseudo-random numbers.
If you want to get random numbers, then learn the <random>. See http://www.cplusplus.com/reference/random/

The rand() and srand() are part of C, not C++, and known to have issues: https://vorbrodt.blog/2019/02/24/rand-considered-harmful/

To my understanding the rand/srand work like these (but with bit larger N):
1
2
3
4
5
6
7
8
9
10
11
12
13
constexpr int N = 10;
int pos = 0;

int rand() {
  static int arr[N] = { 8, 3, 7, 0, 9, 1, 4, 2, 6, 5 };
  int ans = arr[ pos ];
  pos = (pos + 1) % N;
  return ans;
}

int srand( int s ) {
  pos = s % N;
}
> How works the rand() function according to its seed ?
rand() may be implemented as a https://en.wikipedia.org/wiki/Linear_congruential_generator in the standard library.
It's basically a toy for student dice rolling homework, nothing more.
It's certainly no basis for serious statistical or cryptographic usage.

For an LCG, you either get back the old value or the new value.

> srand(s);
> int x = rand() % ratio;
It would seem from some of your results that the first value you get back from rand() is going to be your seed.
In other words, it will seem to count up in step with time.
Calling srand more than one time, especially in a loop, is going to reset the sequence back to the start so you get the same "random numbers" again and again.

The C library's random function, rand(), is bad. Really bad at generating even pseudo random numbers.

https://web.archive.org/web/20180123103235/http://cpp.indi.frih.net/blog/2014/12/the-bell-has-tolled-for-rand/

The C library's srand/rand are broken that even the C standard recommends not using them if there are alternative methods available. C++ introduced those methods with <random>.

https://www.learncpp.com/cpp-tutorial/introduction-to-random-number-generation/

Random Number Generation in C++ (pdf):
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3551.pdf

I created a standalone header/module interface file from the toolkit proposal in that working paper PDF so whenever I need random numbers in my C++ code I simply include the file and generate my sequences. I wrote the functions in a custom namespace and named the functions to replicate the C function names, srand/rand/, with some extra functionality I use quite a lot when generating random numbers along with some error handling capabilities.
Thank you everyone for your clever and relevant explanations. Without any doubt, they are many advantages to use random() instead rand(). Georges, I read your paper right now. I wish you the best. Have a nice day ++
Last edited on
Here's the header-only adaptation for my custom "random toolkit," I named the file "random_toolkit.hpp":
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/* A simple toolkit to help beginners using the <random> library an easier task
 *
 * pre-C++20 header file */

// shamelessly stolen and adapted from a C++ working paper: WG21 N3551
// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3551.pdf

#ifndef RANDOM_TOOLKIT
#define RANDOM_TOOLKIT

#include <chrono>
#include <random>
#include <stdexcept>

namespace rtk
{
   static bool seeded { false };

   inline std::mt19937& urng()
   {
      static std::mt19937 URNG { };

      return URNG;
   }

   // function to initialize (seed) the PRNG engine
   // uses a seed sequence for better randomization
   inline void srand(bool FORCE_SEED = false)
   {
      static const std::seed_seq::result_type seeds[] { std::random_device {} (),
                                                        std::seed_seq::result_type(std::chrono::system_clock::now().time_since_epoch().count()) };
      
      static std::seed_seq sseq(std::begin(seeds), std::end(seeds));

      // the URNG can't be reseeded unless forced
      if (!seeded || FORCE_SEED)
      {
         urng().seed(sseq);

         seeded = true;
      }
   }

   // function to initialize the PRNG engine
   // using a specified seed
   inline void srand(signed seed, bool FORCE_SEED = false)
   {
      // the URNG can't be reseeded unless forced
      if (!seeded || FORCE_SEED)
      {
         urng().seed(static_cast<unsigned>(seed));

         seeded = true;
      }
   }

   // two function overloads to obtain uniform distribution ints and doubles
   inline int rand(int from, int to)
   {
      static std::uniform_int_distribution<> dist { };

      if (from > to) { throw std::invalid_argument("bad int distribution params"); }

      return dist(urng(), decltype(dist)::param_type { from, to });
   }

   inline double rand(double from, double to)
   {
      static std::uniform_real_distribution<> dist { };

      if (from > to) { throw std::invalid_argument("bad double distribution params"); }

      return dist(urng(), decltype(dist)::param_type { from, to });
   }

   // function for rolling dice, and checking if the # of pips is nonstandard
   inline int roll_die(int pips)
   {
      // check to see if the number of die pips is less than 2
      if (pips < 2)
      {
         return -1;
      }

      return rand(1, pips);
   }
}

#endif 

Yes, this looks complicated, a lot of that is from the added functionality I added, but once you have the thing working you don't need to do more than include sight unseen as if it were a standard library header it in your source files to use the toolkit.

Here's a "simple" source file you can use to test the toolkit if you want:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <array>
#include <iostream>
#include <numeric>

#include "random_toolkit.hpp"

int main()
{
   // "create" a random engine and randomize it
   rtk::srand();

   using card = unsigned;

   // manufacture a deck of cards:
   std::array<card, 52> deck { };

   // create the cards, 0 (zero) to 51
   // setting warning level four causes std::iota heartburn from <numeric>
   std::iota(deck.begin(), deck.end(), 0);

   // lambdas to display the card in text representation:
   auto rank = [] (card c) { return "AKQJT98765432"[c % 13]; };
   auto suit = [] (card c) { return "SHDC"[c / 13]; };

   card count { };

   for (card c : deck)
   {
      std::cout << rank(c) << suit(c) << ' ';
      count++;

      if (0 == (count % 13)) { std::cout << '\n'; }
   }
   std::cout << '\n';

   // shuffle the deck:
   std::shuffle(deck.begin(), deck.end(), rtk::urng());

   count = 0;

   for (card c : deck)
   {
      std::cout << rank(c) << suit(c) << ' ';
      count++;

      if (0 == (count % 13)) { std::cout << '\n'; }
   }
   std::cout << '\n';

   for (unsigned loop { }; loop < 50; loop++)
   {
      std::cout << rtk::roll_die(6) << ' ';

      if ((loop + 1) % 20 == 0) { std::cout << '\n'; }
   }
   std::cout << "\n\n";

   // lambda to "flip a coin," returning a text representation of coin side
   auto flip_coin = [] () { return (rtk::rand(0, 1) ? "Heads" : "Tails"); };

   for (size_t loop { }; loop < 25; loop++)
   {
      std::cout << flip_coin() << '\t';

      if ((loop + 1) % 8 == 0) { std::cout << '\n'; }
   }
   std::cout << '\n';

   std::cout << "\nLet's see if we can have a non-standard die.....\nA die with 1 side: ";
   std::cout << rtk::roll_die(1) << '\n';

   std::cout << "A die with zero sides: ";
   std::cout << rtk::roll_die(0) << '\n';

   std::cout << "A die with negative sides: ";
   std::cout << rtk::roll_die(-6) << "\n\n";

   // let's try to create a bad distribution
   // setting warning level 4 causes VS to 'warn' about uninitialized variables
   std::cout << "Creating a bad random distribution, it should be be\n";
   std::cout << "encapsulated within a try/catch block.  Unless you want to crash.\n";

   try
   {
      int itest { rtk::rand(5, 2) };
   }
   catch (const std::exception& e)
   {
      std::cerr << "\n>>> A standard exception was caught, with message '" << e.what() << "'\n";
   }

   try
   {
      double dtest { rtk::rand(5.3, 2.8) };
   }
   catch (const std::exception& e)
   {
      std::cerr << "\n>>> A standard exception was caught, with message '" << e.what() << "'\n";
   }
}

This will compile with C++11 or greater. I use Visual Studio 2019/2022 and can roll back only to C++14 as the earliest standard. It works fine.

Topic archived. No new replies allowed.