Random Number Generation In C++

Overview

Random number generation (RNG) is pivotal in a myriad of applications, from simulating complex systems in scientific research and ensuring fairness in games, to bolstering security in cryptographic protocols. The ability to produce unpredictable and statistically unbiased sequences is crucial for the accuracy of simulations, the integrity of security systems, and the unpredictability of various algorithms. Thus, RNG serves as a foundational tool, driving innovation, ensuring data integrity, and fostering trust in digital systems.

In modern C++, random number generation is facilitated through a combination of engines and distributions. Engines, like std::mt19937, provide the core random sequences, which can be seeded deterministically or with non-deterministic sources like std::random_device. These raw sequences are then transformed by distributions, such as std::uniform_int_distribution<> or std::uniform_real_distribution<>, to produce numbers fitting specific statistical patterns. This approach offers a robust, flexible, and precise method for generating random numbers, catering to a wide range of applications.

Components

Engines

  • These are the core random number generators.

  • Common engines include:

    • std::mt19937: Mersenne Twister with a period of (2^{19937}-1).

    • std::mt19937_64: 64-bit version of Mersenne Twister.

    • std::ranlux48: A high-quality engine with longer cycle.

  • Engines can be seeded with a fixed value or with a value from std::random_device.

Distributions

  • Transform the raw numbers generated by engines into numbers that fit a specific statistical distribution.

  • Common distributions include:

    • std::uniform_int_distribution<>: Produces integers uniformly distributed over a range.

    • std::uniform_real_distribution<>: Produces floating-point numbers uniformly distributed over a range.

    • std::normal_distribution<>: Produces floating-point numbers according to the normal (Gaussian) distribution.

    • std::bernoulli_distribution: Produces boolean values according to a Bernoulli distribution.

  • There are many other distributions available, such as binomial_distribution, exponential_distribution, and more.

std::random_device

  • A non-deterministic random number generator.

  • Often used to seed other engines for truly random sequences.

  • On some platforms, it might be deterministic, so it’s essential to check its entropy before relying on its randomness.

Example

Generating random double values between 1.0 and 2.0 using a fixed seed vs. using std::random_device:

#include <iostream>
#include <random>

int main() {
  // Using a fixed seed
  std::mt19937 engine_fixed_seed(42);

  // Using std::random_device to seed
  std::random_device rd;
  std::mt19937 engine_random_device(rd()); // pay attention to the ()

  // Generate random numbers between 1.0 and 2.0 using a uniform distribution
  // This is a callable object
  // to make random integers between 1 and 100, use this:
  //    std::uniform_int_distribution<int> dist(1, 100)
  std::uniform_real_distribution<double> dist(1.0, 2.0);

  std::cout << "Using fixed seed: " << dist(engine_fixed_seed) << "\n";
  std::cout << "Using random device: " << dist(engine_random_device) << "\n";

  return 0;
}

Comparison with the Old Way

Old Method:

  • Used rand() and srand() functions from the <cstdlib> header.

  • srand() was used to seed the generator, often with the current time.

  • rand() produced integers between 0 and RAND_MAX.

  • To get a random number in a range, modulo arithmetic and scaling were used.

  • WARNING: rand() % n is not a good way to get a random number between 0 and n-1, as it’s biased if RAND_MAX is not divisible by n.

Modern Method:

  • Offers a variety of engines and distributions, providing more flexibility and precision.

  • Produces numbers that adhere to specific statistical distributions.

  • Allows for non-deterministic seeding with std::random_device.

  • Generally more robust and reliable than the old method.

In conclusion, while the old method with rand() and srand() is simpler, it’s less versatile and can have platform-specific behaviors. The modern method in C++ offers a comprehensive suite of tools for random number generation, catering to a wide range of applications and needs.