Mojo Random Numbers

Random numbers power simulations, games, cryptography, statistical sampling, machine learning data augmentation, and testing. Mojo provides a native random module for fast random number generation, and Python's random and numpy.random are also available through the interop layer for richer distributions.

What "Random" Means in Computing

  True random: based on physical unpredictability (radioactive decay,
               atmospheric noise) — used in cryptography.

  Pseudo-random: deterministic sequence that looks random, produced
                 by a mathematical formula starting from a seed value.

  Same seed → same sequence (every time, on every machine).
  Different seed → different sequence.

  Seed 42: [0.37, 0.95, 0.73, 0.15, 0.60, ...]
  Seed 7:  [0.07, 0.43, 0.86, 0.54, 0.20, ...]

  Reproducibility is a feature — fix the seed in tests and experiments
  so results are the same every run.

Mojo's random Module

from random import random_float64, random_si64, random_ui64, seed

fn main():
    seed(42)   # fix the seed for reproducibility

    # Float in [0.0, 1.0)
    var f = random_float64()
    print(f)   # 0.37454... (deterministic with seed 42)

    # Signed integer in the full Int64 range
    var si = random_si64(0, 100)   # in [0, 100)
    print(si)

    # Unsigned integer
    var ui = random_ui64(1, 7)   # simulates a die roll [1, 7)
    print(ui)

Generating a Range of Random Values

from random import random_float64, seed

fn main():
    seed(0)
    var samples = List[Float64]()

    for _ in range(10):
        samples.append(random_float64())

    for i in range(len(samples)):
        print(samples[i], end=" ")
    print("")
Distribution of random_float64():
  0.0 ──────────────────────────── 1.0
  Every point equally likely (uniform distribution)
  |||||||||||||||||||||||||||||||||||
  Each call returns one point in this range.

Scaling to a Custom Range

Scale a [0, 1) float to any [low, high) range with a simple formula.

from random import random_float64, seed

fn random_in_range(low: Float64, high: Float64) -> Float64:
    return low + random_float64() * (high - low)

fn main():
    seed(1)
    for _ in range(5):
        var temp = random_in_range(20.0, 40.0)   # random temperature
        print(temp)
Scaling formula:
  raw  ∈ [0.0, 1.0)
  scaled = low + raw × (high - low)

  Example: low=20, high=40, raw=0.37
  scaled = 20 + 0.37 × 20 = 20 + 7.4 = 27.4

Simulating a Coin Flip

from random import random_float64, seed

fn coin_flip() -> String:
    return "Heads" if random_float64() < 0.5 else "Tails"

fn main():
    seed(99)
    var heads = 0
    var tails = 0
    let trials = 1000

    for _ in range(trials):
        if coin_flip() == "Heads":
            heads += 1
        else:
            tails += 1

    print("Heads:", heads, "| Tails:", tails)
    # Approximately 500/500 with any large trial count
Probability diagram:
  [0.0 ────────── 0.5 ────────── 1.0)
       Tails    │     Heads
               0.5 (threshold)

Shuffling a List

from random import random_ui64, seed

fn shuffle(inout data: List[Int]):
    var n = len(data)
    for i in range(n - 1, 0, -1):
        var j = Int(random_ui64(0, UInt64(i + 1)))
        var temp = data[i]
        data[i] = data[j]
        data[j] = temp

fn main():
    seed(5)
    var deck = List[Int]()
    for i in range(1, 14):   # cards 1–13
        deck.append(i)

    print("Before:", end=" ")
    for i in range(len(deck)):
        print(deck[i], end=" ")
    print("")

    shuffle(deck)

    print("After: ", end=" ")
    for i in range(len(deck)):
        print(deck[i], end=" ")
    print("")
Fisher-Yates shuffle (how shuffle() works):
  Start from the last element and work backward.
  At each position i, pick a random index j in [0, i].
  Swap element i with element j.

  This gives every permutation exactly equal probability.

Sampling Without Replacement

from random import random_ui64, seed

fn sample(data: List[Int], k: Int) -> List[Int]:
    # Copy data into a working list
    var pool = List[Int]()
    for i in range(len(data)):
        pool.append(data[i])

    var result = List[Int]()
    for _ in range(k):
        var idx = Int(random_ui64(0, UInt64(len(pool))))
        result.append(pool[idx])
        # Remove chosen element to avoid re-picking
        pool[idx] = pool[len(pool) - 1]
        _ = pool.pop()

    return result

fn main():
    seed(7)
    var numbers = List[Int](1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    var chosen  = sample(numbers, 3)

    for i in range(len(chosen)):
        print(chosen[i], end=" ")   # 3 unique random picks
    print("")

Richer Distributions via NumPy

from python import Python

fn main() raises:
    var np  = Python.import_module("numpy")
    var rng = np.random.default_rng(seed=42)

    # Normal / Gaussian distribution
    var normal = rng.standard_normal(size=5)
    print("Normal:", normal)

    # Exponential distribution (e.g., wait times)
    var exp_vals = rng.exponential(scale=2.0, size=5)
    print("Exponential:", exp_vals)

    # Binomial (e.g., number of heads in 10 coin flips, repeated 5 times)
    var binom = rng.binomial(n=10, p=0.5, size=5)
    print("Binomial:", binom)

    # Poisson (e.g., events per time period)
    var pois = rng.poisson(lam=3.0, size=5)
    print("Poisson:", pois)
Distribution shapes:

  Uniform:       ████████████████  (flat — all values equally likely)
  Normal:            ▄▄████▄▄      (bell curve — mean is most common)
  Exponential:  █▄▄░░░░░░░░░░░    (many small, few large values)
  Poisson:          ▄▄███▄░░       (count of events in fixed time)

Practical Example: Monte Carlo π Estimation

from random import random_float64, seed

fn estimate_pi(trials: Int) -> Float64:
    seed(0)
    var inside = 0

    for _ in range(trials):
        var x = random_float64()
        var y = random_float64()
        if x*x + y*y <= 1.0:   # point inside unit circle?
            inside += 1

    return 4.0 * Float64(inside) / Float64(trials)

fn main():
    print(estimate_pi(10_000))    # ≈ 3.14
    print(estimate_pi(1_000_000)) # ≈ 3.1416
Monte Carlo π idea:
  ┌──────────────┐
  │  ╭────────╮  │  ← unit square [0,1]×[0,1]
  │ ╱          ╲ │
  ││ (quarter)  ││  ← quarter circle, radius 1
  │ ╲          ╱ │
  │  ╰────────╯  │
  └──────────────┘

  Area of quarter circle = π/4
  Ratio of hits inside circle to total points ≈ π/4
  So π ≈ 4 × (hits / total)

Key Takeaways

Call seed(n) before generating numbers to make results reproducible — essential in testing and scientific experiments. random_float64() returns a float in [0.0, 1.0). Scale it to any range with low + raw * (high - low). Use random_si64(lo, hi) and random_ui64(lo, hi) for integer ranges. Implement the Fisher-Yates algorithm for unbiased list shuffling. Use NumPy's rng for non-uniform distributions (normal, exponential, Poisson). Monte Carlo methods use large numbers of random samples to approximate mathematical constants and integrals.

Leave a Comment

Your email address will not be published. Required fields are marked *