rand() and its operator %

closed account (N8MNAqkS)
can someone explain clearly how to use rand() and what it does, as well as its relationship to %. the guides on these forums are confusing. from what I can gather, rand() randomly, or rather, pseudorandomly, pics a number between specified parameters, for example 1-10. this is probably wrong but that's why I am asking.
https://www.learncpp.com/cpp-tutorial/59-random-number-generation/

If you are creating C++ code using the C library random functions is IMO a very dumb idea. C++11 introduced very useful, very versatile, very robust means and methods for generating random numbers in the <random> library.

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

The basics

rand() is a C library function that pseudo-randomly picks a number between 0 and RAND_MAX, inclusive.

The a % b is an operator that can be basically thought of as giving the remainder of a / b.
e.g. 11 / 5 == 2, 11 % 5 == 1.

0 % 3 == 0
1 % 3 == 1
2 % 3 == 2
3 % 3 == 0
4 % 3 == 1
etc.

Combined, rand() % N gives a pseudo-random number in range [0, N-1], inclusive.

Note that srand() should be called once, preferably at the start of the program, to seed rand().

The issue

rand() is an old function that guarantees almost nothing. It's okay if the implementation makes the least-significant bit alter between 0 and 1 each call, for example. It is not guaranteed to give a uniform distribution, and besides, a uniform distribution, in most cases, is mathematically impossible when combined with the % operator due to the pigeon-hole effect.

Alternative

std::uniform_int_distribution!

C++11 introduced the <random> library, with more verbose, but better quality random numbers.

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

int main()
{
  // obtain a seed from the system clock:
  unsigned seed1 = std::chrono::system_clock::now().time_since_epoch().count();
  // mersenne twister 19937 pseudo-random number generator:
  std::mt19937 generator(seed1);
  // distribution is [1, 6] inclusive, like a die.
  std::uniform_int_distribution<int> distribution(1, 6);

  int nrolls = 20;
  
  for (int i = 0; i < nrolls; i++) {
      std::cout << "Rolled a " << distribution(generator) << "\n";
  }

  return 0;
}

Rolled a 5
Rolled a 3
Rolled a 1
Rolled a 6
Rolled a 1
Rolled a 6
Rolled a 5
Rolled a 5
Rolled a 1
Rolled a 6
Rolled a 3
Rolled a 1
Rolled a 5
Rolled a 1
Rolled a 5
Rolled a 2
Rolled a 5
Rolled a 5
Rolled a 6
Rolled a 3


PS: Here's a link to a thread to a small library that wraps some random number generation:
CSPRNG for everyone! (or Do you hate std::random_device?)
http://www.cplusplus.com/forum/lounge/221672/
Last edited on
Hey, thanks for the plug!

Bias Issue
There are more issues. Using % with rand() introduces bias — and it is all due to the Pigeonhole Principle|https://en.wikipedia.org/wiki/Pigeonhole_principle

The basic idea is this: suppose rand() produced 10 possible numbers: 0..9. But you only want 7 numbers. So you tack on a % 7 to get only 7 possible values.

The problem is that you are still getting ten values.

  0  7  
  0
  1  8  
  1
  2  9  
  2
  3     
  3
  4     
  4
  5     
  5
  6     
  6

See how you get two pigeons in cubbies 0, 1, and 2? That means that you are twice as likely to get a 0, 1, or 2 than, say, a 5.

So unless your RAND_MAX is a multiple of your target range (and it isn’t, since RAND_MAX is (2n - 1)), you will always have a bias towards zero.

The proper way is to pull numbers and simply discard those not in your target range. This is a little trickier than it sounds at first blush, because small ranges mean potential long waits... so you have to find a multiplier such that R*k < N, R*(k+1) > N, get a random number in R, and then use modulo k.

Or you could just use a uniform_int_distribution to do it for you. :O)

Hope this helps.
Last edited on
in practice, rand() is almost always the extremely fast (and rather poor quality) linear congruential design.

If I recall, you get a better uniform from it with rand()/(double)randmax * yourmax. Its still not as good as <random>. I could be misremembering it; I just want random numbers, uniform, not uniform, I usually do not care. The few times I cared I wanted a bell curve, and that I remember for sure, take the average of a bunch of calls and it tends toward the middle in a nice bell.
Last edited on
@jonnin
That is just a 1:1 mapping; it does not change the (poor) characteristics of the underlying PRNG. The Pigeonhole Principle still applies: you may have N cubbies, but you still only have n pigeons to fill them with.

[edit]
The PRNGs in <random> are, excepting Mersenne Twister, all extremely fast.
Last edited on
Ah, you are right of course. That pigeonhole thing gets me every time, I don't think that way naturally and have to draw a picture.

<random> is one of the better additions to the language for sure. Its a little wordy to set it up, but its a great tool.
It took me a while to wrap my head around it too.

That’s why I like making the little graphic. I think the one picture makes it several thousand times easier to grasp.
I wanted a bell curve
<random>'s std::normal_distribution produces real values on a standard normal (Gaussian) distribution. AKA a bell curve.
https://en.cppreference.com/w/cpp/numeric/random/normal_distribution
closed account (N8MNAqkS)
ok so basically because pigeon math, rand and % is no and #include <random> is yes and how exactly do I use this. and how does it work.
closed account (N8MNAqkS)
so basically because pigeon math, rand() is no and <random> and <chrono> is yes but what do these add and how do I use them. I have so much research to do oh good god someone help me :(



im gonna lose my mind




javascript looks easier
someones gonna kill me for saying that.
You want the basics of how to use the C++ random objects, look in the reference section here. More than a few simple examples to get your feet moist.

1. you choose your random number engine: std::default_random_engine is as good as any of the others for generic, trivial usage.

2. you seed your instantiated engine object, using <chrono>'s std::system_clock is a common way to seed.

3. create a distribution to set the range of random numbers each call of your random engine will produce.

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>
#include <chrono>   // for std::chrono::system_clock
#include <random>   // for std::default_random_engine & std::uniform_int_distribution

int main()
{
  // construct a trivial random generator engine from a time-based seed:
   unsigned seed = static_cast<unsigned> (std::chrono::system_clock::now().time_since_epoch().count());

   // seed your chosen random engine
   std::default_random_engine generator(seed);

   // create a distribution that generates random numbers between 1 and 6 to simulate a die.
   std::uniform_int_distribution<unsigned short> distribution(1, 6);

   std::cout << "some random numbers between 1 & 6 inclusive:\n\n";

   for (size_t i = 0; i < 25; ++i)
   {
      std::cout << distribution(generator) << "   ";

      if (((i + 1) % 5) == 0) { std::cout << '\n'; }
   }
   std::cout << '\n';
}
some random numbers between 1 & 6 inclusive:

5   3   1   1   3
6   1   4   4   1
1   3   6   5   5
5   1   6   2   3
5   2   6   4   2


With a header-only tool-kit cobbled from a ISO C++ working paper (WG21 N3551) you can have a drop-in system that makes using the <random> library for most lightweight uses as easy to use as the C library random functions.
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3551.pdf
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
/* A simple toolkit to help beginners using <random> library an easier task */

#ifndef __RANDOM_TOOLKIT_HPP__
#define __RANDOM_TOOLKIT_HPP__

#include <random>
#include <chrono>

namespace rtk
{
   inline std::default_random_engine& urng()
   {
      static std::default_random_engine URNG { };
      return URNG;
   }

   inline void srand()
   {
      static unsigned seed   = static_cast<unsigned> (std::chrono::system_clock::now().time_since_epoch().count());
      static bool     seeded = false;

      if (!seeded)
      {
         urng().seed(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 { };

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

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

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

      return rand(1, pips);
   }
}

#endif 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include "random_toolkit.hpp"

int main()
{
   // initialize the random engine and seed it
   rtk::srand();

   std::cout << "Rolling a 6-sided die:\n\n";

   for (size_t i = 0; i < 25; ++i)
   {
      // roll a six-sided die
      std::cout << rtk::roll_die(6) << "   ";

      if (((i + 1) % 5) == 0) { std::cout << '\n'; }
   }
   std::cout << '\n';
}
Rolling a 6-sided die:

2   3   1   3   2
1   2   1   3   6
3   4   4   5   6
5   1   6   6   6
2   2   5   6   4
Last edited on
javascript looks easier
someones gonna kill me for saying that.

many tools that are easier to use exist. Excel is easier to use too, but it can't do a lot of things :P If the tool does what you need done with the performance and portability you require, use it.

<random> is a minor pain to set up (worse the first few times you do it). Its very flexible and can do a great deal of the things needed in statistics and advanced testing / simulation. You can copy and paste from any tutorial to set it up. After you have used it a few times, it will stop being any more interesting than #include<iostream> which probably seemed really weird back on your first day.

Last edited on
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
#include<iostream>
#include <random>
#include <chrono>
#include <cstdlib>
#include <ctime>

int main()
{
   // get the current system time
   // C: unsigned seedC = (unsigned) std::time(nullptr);
   unsigned seedCPP = static_cast<unsigned> (std::chrono::system_clock::now().time_since_epoch().count());

   // why (unsigned) / static_cast<unsigned> () ?  To stop the warning about possible data loss

   // create a C++ random engine
   // C library automatically creates one: rand()
   std::default_random_engine generator;

   // seed the random number generator
   // std::srand(seedC);
   generator.seed(seedCPP);

   // generate a random number, from engine minimum to engine maximum
   // C: int random_number = rand();
   int random_number = generator();

   std::cout << "A random number: " << random_number << "\n\n";

   // set up a distribution range
   // No C library equivalent
   std::uniform_int_distribution<> distribution(1, 100);

   // create a random number within the range
   // C: random number = rand() % 100 + 1;
   random_number = distribution(generator);

   std::cout << "A ranged random number: " << random_number << '\n';
}
A random number: -353631445

A ranged random number: 83
Topic archived. No new replies allowed.