5.1. Generating Random Numbers#

5.1.1. The Uniform Distribution: Numbers in \([0, 1]\)#

A fundamental tool in computational mathematics is the ability to generate random numbers. The rand(n) function is our primary tool for this, generating \(n\) pseudo-random numbers from a uniform distribution on the interval \([0,1]\).

Why pseudo-random? Because the numbers are generated by a deterministic algorithm. However, the algorithm is designed to produce sequences that are statistically indistinguishable from truly random ones. For nearly all practical purposes, such as statistical simulations, modeling noise, or Monte Carlo methods, we can treat them as genuinely random.

# Generate a single random number.
# If no argument is provided, `rand()` defaults to generating one number.
rand()
0.938683192426478
# Calling the function again produces a new, independent random number.
rand()
0.521370200438274
# To get a vector of random numbers, simply pass the desired size as an argument.
rand(3)
3-element Vector{Float64}:
 0.6630482936961292
 0.38955380670527895
 0.9895799998582973

5.1.1.1. Reproducibility with Seeding#

Since pseudo-random numbers are generated by an algorithm, we can force it to produce the same sequence of numbers every time. This is called seeding the random number generator and is incredibly useful for debugging code or ensuring that scientific results are reproducible.

We can do this using the Random.seed! command from the Random package. You provide an arbitrary integer as the seed.

using Random

# Set the seed for the random number generator.
Random.seed!(1234)
println(rand(3))
println(rand(5))

# Re-seeding with the same number resets the generator to the exact same state.
Random.seed!(1234)
println(rand(2)) # Note: This will be the first part of the sequence generated above.
println(rand(6))
[0.32597672886359486, 0.5490511363155669, 0.21858665481883066]
[0.8942454282009883, 0.35311164439921205, 0.39425536741585077, 0.9531246272848422, 0.7955469475347194]
[0.32597672886359486, 0.5490511363155669]
[0.21858665481883066, 0.8942454282009883, 0.35311164439921205, 0.39425536741585077, 0.9531246272848422, 0.7955469475347194]

5.1.2. Scaling to Other Intervals: Uniform on \([a, b]\)#

To generate uniformly distributed random numbers on an arbitrary interval \([a,b]\), we can perform a linear transformation on the output of rand(). If \(x\) is a random number in \([0,1]\), then the number \(y = a + (b-a)x\) will be a random number in \([a,b]\). Let’s see it in action.

# Generate 5 random numbers on the interval [-1, 1].
# Here, a = -1 and b = 1, so (b-a) = 2. The transformation is y = -1 + 2x.
y = 2 .* rand(5) .- 1
5-element Vector{Float64}:
 -0.011500266219158783
  0.4968300437749482
  0.1564638931227953
  0.4558700024532112
 -0.98510398773427
# Generate 3 random numbers on the interval [4, 11].
# Here, a = 4 and b = 11, so (b-a) = 7. The transformation is y = 4 + 7x.
z = 7 .* rand(3) .+ 4
3-element Vector{Float64}:
 5.395636298694089
 7.0747018781728785
 8.777728635836691

5.1.3. Sampling from Collections: Random Integers and More#

The rand function is very versatile. Beyond generating floating-point numbers, it can also randomly select items from a collection, such as a range of integers or a vector of objects.

# Randomly select 3 integers from the range 1 to 10 (inclusive).
# This is equivalent to rolling a 10-sided die three times.
rand(1:10, 3)
3-element Vector{Int64}:
 10
  7
 10
# We can also sample (with replacement) from any collection of items.
rand([π, exp(1), -1.0], 5)
5-element Vector{Float64}:
 -1.0
  3.141592653589793
  2.718281828459045
  2.718281828459045
  3.141592653589793

5.1.4. The Normal (Gaussian) Distribution#

Another essential distribution is the normal distribution, often visualized as the classic “bell curve.” The function randn() draws random numbers from the standard normal distribution, which has a mean of 0 and a standard deviation of 1.

# Generate one random number from the standard normal distribution.
randn()
-1.973077837133343

Just as with the uniform distribution, we can scale and shift the output of randn() to sample from any normal distribution.

If \(Z\) is a random variable from the standard normal distribution, then the variable \(X = \mu + \sigma Z\) follows a normal distribution with mean \(\mu\) and standard deviation \(\sigma\). Let’s use this to draw from a normal distribution with a mean of 2 and a standard deviation of 5.

# Define the desired mean and standard deviation.
μ = 2
σ = 5

# Generate a standard normal number with randn() and then scale and shift it.
σ * randn() + μ
3.608700549523313