How To Generate Random Numbers In C

9 min read

How to Generate Random Numbers in C

Generating random numbers in C is a fundamental skill for programmers working on simulations, games, or statistical models. The C programming language provides functions in the standard library to produce pseudo-random numbers, which are deterministic but appear random. This guide explains how to use C's built-in functions, addresses common pitfalls, and introduces better alternatives for specific use cases.


Introduction to Random Number Generation in C

Random number generation in C relies on the stdlib.h header file, which declares the rand() and srand() functions. These functions produce pseudo-random numbers, meaning they are generated using a mathematical algorithm that produces a sequence that appears random but is reproducible if the starting point (seed) is known. Understanding how to use these functions effectively is critical for tasks requiring unpredictability Small thing, real impact..

No fluff here — just what actually works.


Using rand() and srand()

The rand() Function

The rand() function generates a pseudo-random integer in the range of 0 to RAND_MAX, where RAND_MAX is a constant defined in stdlib.h. On most systems, RAND_MAX is at least 32767, but it can be larger depending on the implementation The details matter here..

Syntax:

int rand(void);

Example:

#include 
#include 

int main() {
    int random_num = rand();
    printf("Random number: %d\n", random_num);
    return 0;
}

The srand() Function

To ensure different sequences of random numbers each time a program runs, you must seed the random number generator using srand(). Without seeding, rand() will produce the same sequence of numbers every time the program is executed.

Syntax:

void srand(unsigned int seed);

Example:

#include 
#include 
#include 

int main() {
    srand(time(NULL)); // Seed with current time
    int random_num = rand();
    printf("Random number: %d\n", random_num);
    return 0;
}

Why seed with time(NULL)?
The time(NULL) function returns the current calendar time in seconds. By using this as the seed, you confirm that the sequence of random numbers changes with each program run.


Generating Numbers in a Specific Range

While rand() produces numbers between 0 and RAND_MAX, you often need numbers within a specific range. To achieve this, use modular arithmetic:

Formula:

int random_in_range = (rand() % (max - min + 1)) + min;

Example: Generate a number between 1 and 100:

int min = 1;
int max = 100;
int random_num = (rand() % (max - min + 1)) + min;
printf("Random number between 1 and 100: %d\n", random_num);

Note: The modulo operator (%) can introduce bias if the range is not a divisor of RAND_MAX + 1. For critical applications, consider using more advanced methods like rejection sampling.


Limitations of rand() and srand()

1. Predictability

Pseudo-random number generators like rand() are deterministic. If the seed is known, future outputs can be predicted. This makes them unsuitable for cryptographic purposes.

2. Short Period

The period of rand() is limited. On some systems, it may repeat after 32,767 calls, leading to patterns in long-running programs No workaround needed..

3. Low Resolution

rand() returns integers, which may not provide the granularity needed for floating-point probabilities or simulations.


Better Alternatives for Random Number Generation

1. random() and srandom() (Unix/Linux Systems)

Some Unix-like systems provide random() and srandom() functions, which have a longer period (up to 2³¹ − 1) and better statistical properties than rand() Surprisingly effective..

Example:

#include 
#include 

int main() {
    srandom(time(NULL));
    int random_num = random() % 100; // 0–99
    printf("Random number: %d\n", random_num);
    return 0;
}

2. arc4random() (BSD and macOS)

This function, available on BSD-based systems, generates cryptographically secure random numbers without requiring manual seeding Surprisingly effective..

Example:

#include 
#include 

int main() {
    int random_num = arc4random() % 100;
    printf("Random number: %d\n", random_num);
    return 0;
}

Scientific Explanation: How Pseudo-Random Generators Work

Pseudo-random number generators (PRNGs) use algorithms like the linear congruential generator (LCG) to produce sequences of numbers. The general formula for an LCG is:

X_{n+1} = (a * X_n + c) mod m

Where:

  • X_n is the current state (seed).
  • a, c, and m are constants.

The multiplier a controls how the state evolves, the increment c introduces additional variability (when c = 0, the formula reduces to a multiplicative congruential generator), and the modulus m keeps the values within a fixed range. The initial value X_0 is the seed supplied to srand(); every subsequent value is derived strictly from the previous one, which is why knowing the seed makes the entire sequence predictable That's the part that actually makes a difference. Surprisingly effective..

A simple illustration: if a = 1103515245, c = 12345, and m = 2^31, starting from a seed of 1 produces a specific chain of states. Each call to rand() typically

returns the high-order bits of the current state (often by shifting right by 16 bits), so the sequence of visible outputs is a deterministic transformation of the internal state sequence. On top of that, while LCGs are fast and require minimal memory, they suffer from well-known statistical weaknesses: the low-order bits have very short periods, and successive tuples $(X_n, X_{n+1}, X_{n+2})$ fall on a small number of parallel hyperplanes in three-dimensional space—a phenomenon known as the Marsaglia effect. These structural regularities make LCGs unsuitable for Monte Carlo simulations that rely on high-dimensional uniformity, such as numerical integration or particle transport modeling.

Modern PRNGs address these flaws by using larger state spaces and more complex recurrence relations. The Mersenne Twister (MT19937), for instance, maintains a state array of 624 32-bit integers (19,937 bits total) and employs a twisted generalized feedback shift register to achieve a period of $2^{19937}-1$ with 623-dimensional equidistribution. Consider this: this means every possible 623-tuple of 32-bit values appears equally often over the full period, eliminating the hyperplane artifacts inherent in LCGs. On the flip side, even MT19937 is predictable if an attacker observes 624 consecutive outputs, so it remains unsuitable for cryptographic use.


Modern C++: The <random> Library

Since C++11, the standard library provides a professional-grade random-number facility in <random> that separates engines (algorithms that produce uniformly distributed bits) from distributions (transformations that map those bits to specific statistical shapes).

Core Components

Category Examples
Engines std::mt19937 (Mersenne Twister), std::minstd_rand (LCG), std::ranlux24 (subtract-with-carry)
Adaptors std::seed_seq (for seeding), std::independent_bits_engine
Distributions uniform_int_distribution, uniform_real_distribution, normal_distribution, poisson_distribution, exponential_distribution

Idiomatic Usage

#include 
#include 
#include 

int main() {
    // 1. Obtain a high-quality, non-deterministic seed
    std::random_device rd;                          // hardware entropy source (if available)
    std::seed_seq seed{rd(), rd(), rd(), rd()};     // mix multiple samples

    // 2. Initialize engine with the seed sequence
    std::mt19937 gen(seed);                         // 32-bit Mersenne Twister

    // 3. Define the desired statistical distribution
    std::uniform_int_distribution dist(1, 100); // inclusive [1, 100]

    // 4. Generate numbers
    for (int i = 0; i < 5; ++i)
        std::cout << dist(gen) << ' ';
    std::cout << '\n';
    return 0;
}

Key advantages over rand():

  1. No modulo bias – distributions handle range mapping correctly.
  2. Composability – swap engines or distributions without rewriting logic.
  3. Reproducibility – serialize gen state for debugging or checkpointing.
  4. Thread safety – each thread owns its engine instance; no global hidden state.

Cryptographically Secure Random Numbers

For keys, nonces, tokens, or any secret material, statistical quality is insufficient; the generator must withstand state-compromise attacks. Use platform-provided CSPRNGs:

Platform API
Linux / Android getrandom(2) syscall, /dev/urandom
Windows BCryptGenRandom (CNG), RtlGenRandom
macOS / iOS / BSD arc4random_buf, getentropy(2)
C++17+ std::random_device (implementation-defined, but required to be non-deterministic if entropy source exists)

Portable C++17 wrapper:

#include 
#include 
#include 

std::vector secure_bytes(std::size_t n) {
    std::vector buf(n);
    std::random_device rd;
    for (auto &byte : buf) byte = static_cast(rd());
    return buf;
}

Note: On some embedded targets std::random_device may fall back to a PRNG; verify your toolchain’s documentation for security-critical deployments.


Summary of Recommendations

Use Case Recommended Approach
Quick prototypes / teaching demos rand() / srand() (with modulo-bias caveat)
Scientific simulation, games, modeling C++11 <random>:
Use Case Recommended Approach
High‑performance bulk generation Pre‑allocate a vector of pre‑filled values when you know the count ahead of time; reuse it across many calls rather than creating fresh engines per iteration. Think about it:
Parallel computing / multi‑threaded apps Give each thread its own independent engine (and optionally its own distribution object). This avoids contention and preserves reproducibility by seeding each engine separately.
Machine‑learning / stochastic algorithms apply specialized libraries such as Boost.Random, Microsoft’s Microsoft RNG framework, or GPU‑accelerated PRNGs (e.g., cuRAND) when scaling beyond CPU memory limits.
Deterministic simulations Combine a seeded CSPRNG with a fixed‑seeded PRNG for initial warm‑up, then switch entirely to the CSPRNG once the simulation begins—this gives fast deterministic starts while preserving long‑term unpredictability.

Final Thoughts

Choosing the right random‑number strategy hinges on balancing three axes: statistical fidelity, computational cost, and security requirements. And for most application development tasks, the C++ <random> library paired with a modern CSPRNG (std::random_device) offers a compelling middle ground—it provides unbiased, high‑quality distributions out of the box while still being portable enough for cross‑platform deployment. This leads to when the workload demands extreme speed for massive arrays, consider pre‑filling a buffer or delegating to GPU‑based engines. And whenever secrets are involved—encryption keys, session tokens, or cryptographic one‑time pads—the only acceptable choice is a truly non‑deterministic source tied to the system’s entropy reservoir Which is the point..

In practice, the idiomatic workflow is simple yet powerful: obtain a seed via std::random_device, instantiate an engine, select a distribution, and draw. Day to day, by keeping these components decoupled, you gain composability, reproducibility, and the ability to swap implementations without touching the core business logic. Remember, however, that the quality of randomness matters far more than tweaking the algorithm itself—if the underlying RNG produces biased samples, even the most sophisticated composition will propagate those errors throughout your program Simple as that..

Adopting this pattern not only future‑proofs your codebase against changing standards (e., upcoming changes to the CSPRNG interface) but also makes debugging easier thanks to the explicit separation between state management and value generation. Even so, g. With careful attention to seeding, distribution selection, and security concerns, random number generation becomes a reliable, maintainable, and performant pillar of software design And that's really what it comes down to..

The official docs gloss over this. That's a mistake.

Just Shared

Fresh Content

Try These Next

Based on What You Read

Thank you for reading about How To Generate Random Numbers In C. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home