Skip to content
/ zrand.h Public

A statistically robust, header-only PCG random number library for C/C++.

License

Notifications You must be signed in to change notification settings

z-libs/zrand.h

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

zrand.h

zrand.h is a high-performance, cross-platform Random Number Generator (RNG) library for C and C++. It implements the PCG-XSH-RR algorithm, which offers statistically superior randomness compared to the standard rand() (Linear Congruential Generator) while remaining extremely fast and memory-efficient.

It also includes a robust C++11 wrapper (z_rand namespace) that integrates with STL containers and provides a modern, fluent API.

Features

  • Statistically Robust: Uses the PCG algorithm (Permuted Congruential Generator). Passes rigorous statistical tests (TestU01/BigCrush) where standard rand() fails.
  • Bias-Free Ranges: Implements rejection sampling for zrand_range(), eliminating the "modulo bias" inherent in rand() % N.
  • Cross-Platform Entropy: Automatically seeds from the OS CSPRNG (/dev/urandom on Linux/macOS, rand_s on Windows).
  • Thread Safety: Uses thread-local state by default. No locks required for global generation.
  • Deterministic Mode: Supports explicit seed/sequence initialization for replay systems or procedural generation.
  • Gaussian Distribution: Built-in Box-Muller transform for normal distributions.
  • Zero Dependencies: Standard C headers only. Can optionally use zmath.h for math functions to avoid linking -lm, or falls back to <math.h>.
  • Header Only: No linking required.

Installation

  1. Copy zrand.h to your project's include folder.
  2. In exactly one source file (.c or .cpp), define the implementation macro ZRAND_IMPLEMENTATION before including the header.
#define ZRAND_IMPLEMENTATION
#include "zrand.h"

Optional: zmath.h Integration

If you want to avoid linking against the standard math library (-lm) or want a strictly freestanding environment, you can use zmath.h. The provided Makefile handles this automatically for testing:

# For running the tests. Will get and remove 'zmath.h'.
make test

# Or if you just want to get 'zmath.h'.
make

Usage: C

For C projects, you can use the global API for immediate results without managing state. The library handles seeding automatically.

#define ZRAND_IMPLEMENTATION
#define ZRAND_SHORT_NAMES
#include "zrand.h"
#include <stdio.h>

int main(void)
{
    // Auto-seeds from OS entropy on first use. No srand() needed.
    
    // Basic generation.
    printf("d20 Roll: %d\n", rand_range(1, 20));
    printf("Float (0..1): %.4f\n", rand_f32());

    // Utilities.
    if (rand_chance(0.05)) 
    {
        printf("Critical Hit! (5%% chance)\n");
    }

    // Gaussian (mormal) distribution (Mean=0, StdDev=1).
    printf("Normal Value: %.4f\n", rand_gaussian(0.0, 1.0));

    // UUID generation.
    char uuid[37];
    rand_uuid(uuid);
    printf("New ID: %s\n", uuid);

    return 0;
}

Note: You can use #define ZRAND_SHORT_NAMES to access functions as rand_xyz instead of zrand_xyz.

Usage: C++

The library detects C++ automatically. The wrapper lives in the z_rand namespace.

#include <iostream>
#include <vector>
#include "zrand.h"

int main()
{
    // A native-like API.
    std::cout << "d6 Roll: " << z_rand::range(1, 6) << "\n";
    std::cout << "UUID: " << z_rand::uuid() << "\n"; // Returns std::string.

    // STL integration.
    std::vector<int> deck = {1, 2, 3, 4, 5};
    z_rand::shuffle(deck); // Works directly on std::vector.

    // Deterministic Instance.
    z_rand::generator rng(12345); // Fixed seed.
    std::cout << "Replay Value: " << rng.range(1, 100) << "\n";

    return 0;
}

API Reference (C)

Global Generation

These functions use a high-performance thread-local generator instance. It is automatically seeded from the OS on the first call in each thread.

Core Generators

Function Description
uint32_t zrand_u32(void) Returns a random 32-bit unsigned integer.
uint64_t zrand_u64(void) Returns a random 64-bit unsigned integer.
float zrand_f32(void) Returns a float in the range [0.0, 1.0) (exclusive).
double zrand_f64(void) Returns a double in the range [0.0, 1.0) (exclusive).
bool zrand_bool(void) Returns true or false (50/50 probability).

Utilities

Function Description
int32_t zrand_range(int32_t min, int32_t max) Returns int32_t in range [min, max] (inclusive). Bias-free.
float zrand_range_f(float min, float max) Returns float in range [min, max).
bool zrand_chance(double probability) Returns true if a random check passes the given probability (0.0 to 1.0).
double zrand_gaussian(double mean, double stddev) Returns a double following a normal distribution.
void zrand_bytes(void *buf, size_t len) Fills a buffer with random bytes.
void zrand_str(char *buf, size_t len) Fills a buffer with a random alphanumeric string (A-Z, a-z, 0-9).
void zrand_uuid(char *buf) Generates a valid UUID v4 string. buf must be at least 37 bytes.
void zrand_shuffle(void *base, size_t nmemb, size_t size) Shuffles an array in-place using Fisher-Yates.
void* zrand_choice(void *base, size_t nmemb, size_t size) Returns a void* pointer to a random element in the array.

Management

Function Description
void zrand_init(void) Explicitly re-seeds the current thread's generator from OS entropy (/dev/urandom or rand_s).

Instance API (Deterministic)

Use these functions when you need reproducible sequences (e.g., for game replays, procedural generation, or simulations) where the randomness must be identical given the same seed.

Management

Function Description
void zrand_rng_init(zrand_rng *rng, uint64_t seed, uint64_t seq) Initializes a specific zrand_rng struct. seq (sequence) allows different streams from the same seed.

Generation

Function Description
uint32_t zrand_rng_u32(zrand_rng *rng) Helper to generate a 32-bit int from a specific instance.
uint64_t zrand_rng_u64(zrand_rng *rng) Helper to generate a 64-bit int from a specific instance.
int32_t zrand_rng_range(zrand_rng *rng, int32_t min, int32_t max) Helper to generate a range from a specific instance.
double zrand_rng_f64(zrand_rng *rng) Helper to generate a double from a specific instance.
double zrand_rng_gaussian(zrand_rng *rng, double mean, double stddev) Helper to generate a gaussian double from a specific instance.

API Reference (C++)

The C++ wrapper is defined in the z_rand namespace.

Global Shortcuts

Function Description
z_rand::init() Wraps zrand_init.
z_rand::u32(), u64() Returns random integers.
z_rand::f32(), f64() Returns random floating point numbers.
z_rand::boolean() Returns boolean.
z_rand::chance(prob) Returns true based on probability.
z_rand::range(min, max) Returns value in range (overloaded for int and float).
z_rand::gaussian(mean, std) Returns normally distributed value.
z_rand::uuid() Returns a standard std::string containing a UUID v4.
z_rand::string(len) Returns a standard std::string of random alphanumeric characters.
z_rand::shuffle(vector) Shuffles a std::vector (or z_vec::vector) in-place.
z_rand::choice(vector) Returns a random element (const ref) from the vector.

Deterministic Generator

The z_rand::generator class wraps the C struct state (zrand_rng). It provides methods matching the global API but operates on its own internal state.

// Seed 1234, Sequence 1.
z_rand::generator rng(1234);

int hp = rng.range(50, 100);
bool has_key = rng.chance(0.25);

Why PCG?

The default rand() in C is typically a Linear Congruential Generator (LCG). LCGs have statistical flaws:

  1. Short Periods: They repeat sequences relatively quickly.
  2. Poor Distribution: They can fail basic statistical tests (like the "Birthday Spacings" test).
  3. Low Bits: The lower bits of an LCG often exhibit visible patterns (odd/even/odd/even).

PCG (Permuted Congruential Generator) solves this by applying a permutation function (XSH-RR) to the output of an LCG. This scrambles the bits, resulting in:

  • Excellent statistical quality (passes BigCrush).
  • Performance that is often faster than rand() due to modern CPU optimizations.
  • A huge period (2^64), making repeats virtually impossible in typical applications.

About

A statistically robust, header-only PCG random number library for C/C++.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

  •  

Packages

No packages published