Cromulent PRNG
A modern Pseudo-Random Number Generator (PRNG) implemented in C.
PractRand 128TB test results here
Overview
Cromulent PRNG is a portable C library providing a high-quality random number generation algorithm with a focus on performance, statistical quality, and ease of use. The library includes both scalar implementations for general use and SIMD-accelerated variants for x86_64 platforms with AVX2 support.
Features
-
High-performance
cromulent128PRNG with 128-bit state -
Optimized implementations:
- Scalar code for all platforms
- AVX2-accelerated implementation for x86_64 platforms
-
Comprehensive API:
- Basic operations: initialization, next value
- Utilities: uniform doubles/floats, bounded ranges
- State management: save/load for reproducibility
- Jump-ahead functionality for stream separation
-
Built-in benchmarking and testing tools
-
Small footprint with minimal dependencies
Requirements
- C11 compatible compiler
- CMake 3.19 or higher
- (Optional) AVX2 support for SIMD acceleration
- (Optional) 128-bit integer support (
__uint128_t) for faster range generation
Building
mkdir build && cd build cmake .. make
For optimized builds:
cmake -DCMAKE_BUILD_TYPE=Release .. make
Installation
This will install the library and headers to your system.
Usage
Basic Example
#include "cromulent.h" #include <stdio.h> int main() { // Initialize the PRNG with a seed cromulent_state state; cromulent_init(&state, 12345); // Generate and print 10 random numbers for (int i = 0; i < 10; i++) { printf("%lu\n", cromulent_next(&state)); } // Generate a random double in [0, 1) double rand_double = cromulent_double(&state); printf("Random double: %f\n", rand_double); // Generate a random integer in range [0, 99] uint64_t rand_range = cromulent_range(&state, 100); printf("Random in range [0, 99]: %lu\n", rand_range); return 0; }
The cromulent_range function relies on 128-bit integer arithmetic when the compiler provides __uint128_t. A portable 64-bit implementation is used as a fallback.
Saving and Loading State
// Save the current state uint8_t buffer[16]; // cromulent128 state is 16 bytes cromulent_save(&state, buffer); // ... later or in another program ... // Restore the state cromulent_load(&state, buffer); // Continue generating the same sequence from this point
cromulent_save writes the two 64-bit words of the PRNG state in
little-endian byte order. The data in buffer is therefore portable
between platforms with different native endianness.
Using the Generator Registry
The library maintains a registry system primarily for internal benchmarking and testing, but it can also be used in applications:
// Get a reference to the Cromulent PRNG const CromulentPRNG *gen = cromulent_registry_find("cromulent128"); if (gen) { gen->init(12345); uint64_t random_value = gen->next(); }
Benchmark Results
The library includes a micro-benchmark tool (bench_micro) that measures the performance of the cromulent128 PRNG algorithm. Here's a sample of expected performance on a modern CPU:
running 10000000000 samples with seed 69420
xoshiro256 : 4.00 ns/sample, dummy=15115886174096218845
cromulent128 : 2.93 ns/sample, dummy=8577353182525494841
splitmix64 : 1.27 ns/sample, dummy=12147880745723112187
pcg64 : 1.76 ns/sample, dummy=13280616445415795540
Exact numbers will vary based on your hardware.
Testing
The library includes a basic sanity test suite. Run it with:
Design Philosophy
Cromulent PRNG aims to provide:
- Speed: Optimized for modern CPUs with careful attention to instruction-level parallelism
- Quality: All generators pass stringent statistical tests
- Simplicity: Clean API with minimal dependencies
- Portability: Works across platforms while taking advantage of hardware acceleration when available
Technical Details
State Size and Period
cromulent128: 128-bit state (2 × 64-bit words), period approximately 2^128
Output Mixing
The generator uses a carefully designed output mixing function to improve statistical distribution and quality, especially for low-order bits.
Jump Operations
The library supports jump-ahead operations, allowing you to efficiently advance the state by 2^64 steps. This is useful for creating independent, non-overlapping streams for parallel applications.

