Introduction

Pseudo-random number generators (PRNGs) power much of what goes on “behind the scenes” in statistical and cryptographic settings. In R, there is a pseudo-random number generator present which allows users to generate random variables from a variety of distributions. To check the default PRNG used by your R version you can run the following:

# Using version 4.2.2 (at time of writing)
# Returns the methods used for
# 1. The "default" random number generation, 
# 2. Normal variable generation 
# 3. Discrete uniform variable generation

RNGkind()
#> [1] "Mersenne-Twister" "Inversion"        "Rejection"

The PRNG which R utilizes can be specified by specifying a .Random.seed argument in the beginning of an R script or by adjusting the default kind (and normal.kind and sample.kind) arguments in the RNGkind or set.seed argument.

While it is possible for one to utilize their own PRNG through use of user-supplied random number generation, such an approach is pedagogically complex. For users interested in working with and learning about random number generation, present tools available still leave a degree of complexity and/or mystery around how random numbers are generated from the seed(s) and parameters supplied to a given PRNG. The randngen package provides a suite of PRNGs to which aim to be easy to use and flexible for understanding the relevant maths and algorithms implemented by allowing users to specify all relevant parameters.

From PRNG to Uniform Random Variables (and beyond!)

library(randngen)

# Function from here: https://stackoverflow.com/questions/5468280/scale-a-series-between-two-points
range01 <- function(x){(x-min(x))/(max(x)-min(x))}


randngen::lcg(seed = 1234, n=10000)|>
  range01()|>
  hist(main= "0-1 Scaled Uniform Random Variables with randgen::lcg()")

set.seed(1234)
runif(10000)|>
  hist(main= "0-1 Uniform Random Variables with runif()")

Applying the Inverse Probability Transform to Generate Random Variables

library(randngen)

randngen::lcg(seed = 1234, n=10000)|>
  range01()|>
  qnorm()|>
  hist(main= "Normal(0,1) Random Variables using\nscaled randgen::lcg() values and qnorm()")

set.seed(1234)
runif(10000)|>
  qnorm()|>
  hist(main= "Normal(0,1) Random Variables using runif() and qnorm()")

Other applications

  • Simulation studies: use the same PRNG and seed values for your research and not have to worry about

Appendix

R’s history with PRNGs

Disclaimer: This section was copied from output from Gemeni. I have not found information on this collected in one individual place. However I was told by ChatGPT that it “[…] is well-documented in R’s official NEWS files, which detail changes and new features introduced in each R version …”. If this information is misleading or false, please open an issue or submit a pull request with more accurate information. ## R’s History with PRNGs

R’s base distributions have steadily evolved their Pseudo-Random Number Generators (PRNGs) to prioritize longer periods, better equidistribution, and improved accuracy. Here is an accurate timeline of R’s PRNG infrastructure.

1. Early Versions of R (Pre-1.7.0)

  • Default PRNG: Marsaglia-Multicarry
  • Default Normal RNG: Kinderman-Ramage
  • Context: Early R relied heavily on George Marsaglia’s multiply-with-carry approach, which offered a period of over 2^60.
  • Legacy: The original normal random number generator was later found to have approximation issues in the tails of the distribution. To ensure older scripts remain reproducible, R preserves this specific legacy algorithm under the name "Buggy Kinderman-Ramage". Other uniform alternatives available at the time included Wichmann-Hill and Super-Duper.

2. R 1.7.0 Series (April 2003)

  • Default PRNG: Mersenne Twister (MT19937)
  • Default Normal RNG: Inversion
  • Context: This major update shifted the default uniform PRNG to the Mersenne Twister, known for its massive cycle period of 2^19937 - 1 and strong equidistribution properties.
  • Reproducibility: To handle the breaking change in random streams, this release introduced the RNGversion() function, allowing users to explicitly request the algorithms used in older versions of R.

3. Introduction of L’Ecuyer-CMRG

  • Default PRNG: Mersenne Twister (Maintained)
  • Context: R introduced the L’Ecuyer-CMRG (MRG32k3a) algorithm to solve the problem of random number generation in parallelized code.
  • Use Case: It provides multiple independent streams of random numbers, ensuring that parallel simulations do not suffer from overlapping random sequences or compromised statistical properties.

4. R 3.6.0 Series (April 2019)

  • Default PRNG: Mersenne Twister (Maintained)
  • Context: R fundamentally changed how discrete uniform random numbers are generated, directly impacting functions like sample().
  • Technical Fix: Previous versions used a modulo arithmetic method that introduced a slight bias in discrete sampling. R 3.6.0 replaced this with a rejection sampling method, ensuring perfectly uniform probabilities when drawing integers.

5. R 4.x Series (Current Behavior)

  • Default PRNG: Mersenne Twister (Maintained)
  • Context: Base R continues to rely on MT19937 as the default, with L’Ecuyer-CMRG recommended for multi-threading.
  • Ecosystem Expansion: Base R natively lacks modern, state-of-the-art generators like the xoshiro or xoroshiro families. Users requiring these high-performance algorithms must rely on external packages, such as randngen, to bridge the gap between base R’s established defaults and cutting-edge PRNG research.

Summary of Defaults and Capabilities

R Version / Era Default PRNG Normal RNG Key Updates & Notes
Before R 1.7.0 Marsaglia-Multicarry Kinderman-Ramage Legacy methods preserved via "Buggy Kinderman-Ramage"
R 1.7.0 Series Mersenne Twister Inversion MT19937 becomes the standard; RNGversion() introduced
Later Additions Mersenne Twister Inversion L’Ecuyer-CMRG added for parallel processing and independent streams
R 3.6.0 Mersenne Twister Inversion Modulo-based discrete sampling replaced with rejection sampling in sample()
R 4.x (Current) Mersenne Twister Inversion Relies on external packages like randngen for xoshiro variants

Reproducibility Across Versions

R allows explicit control of the RNG type and seed via RNGkind() and set.seed(), respectively, ensuring reproducibility even as PRNG options expand.

References

  1. Random function - RDocumentation. https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/Random.

  2. Morris, T. P., White, I. R. & Crowther, M. J. Using simulation studies to evaluate statistical methods. Statistics in Medicine 38, 2074–2102 (2019).

  3. Generating distributions from random number generators. Cross Validated https://stats.stackexchange.com/questions/637706/generating-distributions-from-random-number-generators.

  4. L’Ecuyer, P. & Simard, R. TestU01. ACM Transactions on Mathematical Software 33, 1–40 (2007).

  5. R Core Team. R News Files. https://cran.r-project.org/doc/manuals/r-release/