fast-prng-wasm
High-performance, SIMD-enabled, WebAssembly pseudo random number generators (PRNGs) with a seamless TypeScript interface. Faster and better statistical quality than Math.random()
.
Perfect for: Simulations, Monte Carlo methods, games, procedural generation, parallel computations
- Quick Start
- Features
- PRNG Algorithms
- Usage Guide
- API Documentation
- Examples & Demos
- Performance
- Compatibility
- Contributing
Quick Start
npm install fast-prng-wasm
import { RandomGenerator } from 'fast-prng-wasm';
const gen = new RandomGenerator(); // Xoroshiro128+ SIMD is default
console.log(gen.float()); // random 53-bit float (number) in [0, 1)
console.log(gen.int53()); // random 53-bit int (number)
console.log(gen.int64()); // random 64-bit int (bigint)
Features
- 🚀 High Performance - Optimized for speed and SIMD-accelerated
- 📊 Better Statistical Quality - Superior uniformity and randomness vs.
Math.random()
- ⚡ Bulk Generation - Single values or bulk array fills
- 🎯 Simple API - Clean TypeScript interface, no memory-management required
- 🔢 Multiple Formats - 64-bit
bigint
, 53-bit int, 32-bit int, and 53-bit float - 🌱 Seedable - Full control over initialization (or automatic seeding)
- 🧵 Parallel-Ready - Unique stream selection for multi-threaded applications
- ✨ Zero Config - Synchronous WASM loading, no
fs
orfetch
required - 🌐 Universal - Works in Node.js 16.4+ and all modern browsers
- 📦 AssemblyScript Library - Can be imported to larger WASM project builds
PRNG Algorithms
Algorithm | Description | Native Output | State Size | Period | SIMD |
---|---|---|---|---|---|
Xoshiro256+ | Very fast, large state, very long period - best for applications needing maximum randomness guarantees | 64-bit | 256 bits | 2256 | ✅ |
Xoroshiro128+ | Very fast, smaller state - excellent balance for most applications, fastest provided here | 64-bit | 128 bits | 2128 | ✅ |
PCG (XSH RR) | Small state, fast, possibly best randomness (read Learn More links) | 32-bit | 64 bits | 264 | ❌ |
The included algorithms were chosen for their high speed, parallelization support, and statistical quality. They pass rigorous statistical tests (BigCrush, PractRand) and provide excellent uniformity, making them suitable for Monte Carlo simulations and other applications requiring high-quality pseudo-randomness. They offer a significant improvement over Math.random()
, which varies by JavaScript engine and may exhibit statistical flaws.
SIMD (Single Instruction, Multiple Data) generates 2 random numbers simultaneously, theoretically doubling throughput when using array output methods.
⚠️ Security Note: These PRNGs are NOT cryptographically secure. Do not use for cryptography or security-sensitive applications, as they are not resilient against attacks that could reveal sequence history.
Learn More
- PCG: A Family of Better Random Number Generators
xoshiro
/xoroshiro
generators and the PRNG shootout
Usage Guide
Importing
ES Module (bundler / modern browser / modern Node)
import { RandomGenerator, PRNGType, seed64Array } from 'fast-prng-wasm';
CommonJS (legacy Node)
const { RandomGenerator, PRNGType, seed64Array } = require('fast-prng-wasm');
UMD (browser script tag)
<script src="https://unpkg.com/fast-prng-wasm"></script>
<script>
const { RandomGenerator, PRNGType, seed64Array } = fastPRNGWasm;
</script>
The Basics
const gen = new RandomGenerator(); // Xoroshiro128Plus_SIMD, auto-seeded
console.log(gen.int64()); // unsigned 64-bit int (bigint)
console.log(gen.int53()); // unsigned 53-bit int (number)
console.log(gen.int32()); // unsigned 32-bit int (number)
console.log(gen.float()); // 53-bit float (number) in [0, 1)
console.log(gen.coord()); // 53-bit float (number) in (-1, 1)
console.log(gen.coordSquared()); // 53-bit float (number) in (-1, 1) squared
const pcgGen = new RandomGenerator(PRNGType.PCG);
console.log(pcgGen.int64());
// ... etc - all PRNG types expose the same JS/TS interface
The internal WASM binary is instantiated automatically when a RandomGenerator
instance is created.
Array Output (Bulk Array Fill)
The fastest way to get random numbers in bulk is to use the *Array()
methods of RandomGenerator
. Each call fills a WASM memory buffer with the next 1000 (by default) random numbers, and returns a view of the buffer as an appropriate TypedArray
: either BigUint64Array
for int64Array()
, or Float64Array
for all methods.
💡 SIMD Performance: Array methods MUST be used to realize the additional throughput offered by SIMD-enabled PRNG algorithms. These have higher throughput because they produce 2 random numbers at the same time with WASM's 128-bit SIMD support) .
Bulk Fill Example
const gen = new RandomGenerator();
// `bigint`s in BigUint64Array
const bigintArray = gen.int64Array(); // 1000 64-bit integers
// `number`s in Float64Array
let numberArray = gen.int53Array(); // 1000 53-bit integers
numberArray = gen.int32Array(); // 1000 32-bit integers
numberArray = gen.floatArray(); // 1000 floats in [0, 1)
WASM Array Memory Buffer
⚠️ Reused Buffer Warning: The array returned by these methods is actually a
DataView
looking at a portion of WebAssembly memory. This memory buffer is reused between calls to the*Array()
methods (to minimize WASM-JS boundary crossing time), so you must actually consume (e.g. read/copy) the output between each call.
const gen = new RandomGenerator();
// ⚠️ Warning: Consume before making another call to any method that returns a Float64Array
const array1 = gen.floatArray(); // 1000 floats in [0, 1)
console.log(array1); // consume (extract random results)
// Values originally in array1 have been replaced! (despite different local variable)
const array2 = gen.floatArray(); // 1000 new floats in [0, 1)
console.log(array2); // consume again (extract more random results)
console.log(array1 === array2); // true (same array in memory)
console.log(array1[42] === array2[42]); // true (second call refilled the same array)
Set Array Output Size
If you don't need 1000 numbers with each method call, you can specify your preferred size for the output array instead. Note that an array larger than the default of 1000 does not increase performance further in test scenarios.
// Set size of output buffer to 200
// - `null` for `seeds` param will auto-seed
// - `null` for `uniqueStreamId` param will use default stream
const gen = new RandomGenerator(PRNGType.PCG, null, null, 200);
let randomArray = gen.floatArray(); // 200 floats in [0, 1)
// Resize output buffer to 42
gen.outputArraySize = 42; // change output array size
randomArray = gen.floatArray(); // 42 floats in [0, 1)
⚙ Configuration Note: The AssemblyScript compiler configuration in
asconfig.release.json
specifies a fixed WASM memory size of 1 page. This is intentionally kept small to limit resources allocated to WASM instances, but is still enough space for the default of 1000 numbers.
// exceeds the configured memory limits of WASM instances gen.outputArraySize = 5000; // Runtime Error ⚠️
Manual Seeding
Manual seeding is optional. When no seeds are provided, a RandomGenerator
will seed itself automatically.
Manual seeding is done by providing a collection of bigint
values to initialize the internal generator state. Each generator type requires a different number of seeds (between 1 and 8). The required count for a specific PRNG is exposed via RandomGenerator
's seedCount
property, as well as in the SEED_COUNT
variable and setSeed()
function signature in the AssemblyScript API.
const customSeeds = [7n, 9876543210818181n]; // Xoroshiro128+ takes 2 bigint seeds
const customSeededGen = new RandomGenerator(PRNGType.Xoroshiro128Plus, customSeeds);
const anotherGen = new RandomGenerator(PRNGType.Xoshiro256Plus);
console.log(anotherGen.seedCount); // 4
Using high quality seeds is important, as summarized on Vigna's Xoshiro page:
We suggest to use SplitMix64 to initialize the state of our generators starting from a 64-bit seed, as research has shown that initialization must be performed with a generator radically different in nature from the one initialized to avoid correlation on similar seeds.
Per this guidance, automatic seeding using seed64Array()
internally is done with SplitMix64.
Parallel Generators & Sharing Seeds
Some PRNG applications may require several (or very many) instances of a PRNG running in parallel - for example, multithreaded or distributed computing processes. In this case it is recommended to use the same set of seeds across all parallel generator instances in combination with a unique jump count or stream increment. This approach essentially ensures that randomness quality is maximized across all parallel instances.
See the pmc
demo for an example that follows this approach, with each generator instance running in a different Node worker thread.
Generate a Seed Collection
If you don't have custom seeds already, the seed64Array()
function is provided. It returns a bigint[8]
containing seeds generated with SplitMix64 (which in turn was seeded with a combination of the current time and JavaScript's Math.random()
). This collection can be provided as the seeds
argument for any PRNG in this package.
Choose a Unique Stream for Each Parallel Generator
Sharing seeds between generators assumes you will also provide a unique uniqueStreamId
argument:
- For the PCG PRNG, this will set the internal increment value within the generator, which selects a unique random stream given a specific starting state (seed).
- For Xoshiro family PRNGs, this will advance the initial state (aka
jump()
) to a unique point within the generator period, allowing for effectively the same behavior - choosing a non-overlapping random stream given a specific starting state
In both cases, this value is simply a unique positive integer (the examples below provide this as bigint
literals).
Examples
const sharedSeeds = seed64Array(); // bigint[8]
// Two PCG generators, using the same seeds but choosing unique stream increments (5n vs 4001n)
const pcgGen1 = new RandomGenerator(PRNGType.PCG, sharedSeeds, 5n);
const pcgNum1 = pcgGen1.float();
const pcgGen2 = new RandomGenerator(PRNGType.PCG, sharedSeeds, 4001n);
const pcgNum2 = pcgGen2.float();
console.log(pcgNum1 === pcgNum2); // false
// Two Xoshiro256+ generators using the same seeds, but with unique jump counts (1n vs 13n)
const seededGen1 = new RandomGenerator(PRNGType.Xoshiro256Plus_SIMD, sharedSeeds, 1n);
const num1 = seededGen1.float();
const seededGen2 = new RandomGenerator(PRNGType.Xoshiro256Plus_SIMD, sharedSeeds, 13n);
const num2 = seededGen2.float();
console.log(num1 === num2); // false
// Another Xoshiro256+ generator using the same seeds, and same jump count (13n) as seededGen2.
// ⚠️ seededGen2 and seededGen3 are effectively identical and will return the same random stream.
const seededGen3 = new RandomGenerator(PRNGType.Xoshiro256Plus_SIMD, sharedSeeds, 13n);
const num3 = seededGen3.float();
console.log(num2 === num3); // true: using same seeds and same uniqueStreamId!!
Using from AssemblyScript Projects
// import the namespace(es) you want to use
import { PCG, Xoroshiro128Plus } from 'fast-prng-wasm/assembly';
Xoroshiro128Plus.setSeeds(57n, 1000123n); // manually seeded - seed64Array() only in JS
const rand: u64 = Xoroshiro128Plus.uint64(); // using the AS interface
const rand2: f64 = Xoroshiro128Plus.uint53AsFloat(); // return types are cast for JS runtime usage
const arr = new Uint64Array(1000); // create array in WASM memory
Xoroshiro128Plus.uint64Array(arr); // generate & fill
⚠️ Thread Safety Warning ⚠️: WASM PRNG implemetations use top-level internal state and functions to prevent the accumulation of small overhead that comes with using classes.
While they are encapsulted within namespaces so as not to interfere with your own AssemblyScript project's global namespace, this also means that they are NOT THREAD SAFE WITHIN WASM DIRECTLY.
To acheive thread safety from the JS runtime calling your AssemvblyScript WASM project binary, it must be structured in such a way as to create separate WASM instances from JS. This is the approach used by the included JavaScript/TypeScript wrapper API.
API Documentation
Examples & Demos
See the examples/
folder for all available examples and demos.
basic-usage
- Getting Started
A simple walkthrough of core features
- Quick start for new users
- Covers all major API methods
- Basic performance comparisons and tips
- Practical examples (dice simulator, Monte Carlo basics)
pmc
- Pi Monte Carlo
A Monte Carlo statistical estimation of π (pi) using a large quantity of random numbers
- Node CLI app for advanced users
- Uses parallel generator instances in
worker_threads
- Shares seeds across instances, using a unique jump count / stream increment for each
Performance
The goal is to provide random number generation in WASM that's faster and higher-quality than Math.random()
, and faster than any equivalent JavaScript implementation of these PRNG algorithms.
Generator algorithms are implemented in AssemblyScript, a variant of TypeScript that compiles to WASM.
Key performance advantages:
- PRNG algorithms chosen for speed
- WASM is faster than JS by design
- AssemblyScript project structure and compilation are optimized for speed
- Bulk array generation minimizes JS/WASM boundary crossing overhead
- Reusing WASM array memory avoids alloc delays and heap fragmentation
- SIMD acceleration can nearly double throughput for supported algorithms
- Monte Carlo unit square vs unit circle test included for validation
⚡ Additional performance stats coming soon! ⚡
Arrays Deep Dive
For a detailed explanation of why array methods are 3-5× faster than single-value methods, see Understanding Performance: Why Array Methods Are Faster
Compatibility
Node
Version | Notes |
---|---|
16.4+ | Full support |
15.0 | All features except SIMD |
8.5.7 - 15 | No SIMD, No bigint |
Node versions older than 15 are not recommended, as they lack support for
i64
tobigint
conversion. Versions going back to 8.5.7 (first WASM support in Node) should otherwise still work correctly for 53-bit integer and floatnumber
values, though they haven't been fully tested.
Browsers
All modern browsers are fully supported.
Browser | Full Support | Partial (No SIMD) | Degraded (No bigint ) |
---|---|---|---|
Chrome | 91+ | 85 | 57 |
Edge | 91+ | 85 | 16 |
Safari | 16.4+ | 14.1 | 11 |
Firefox | 89+ | 78 | 52 |
Opera | 77+ | 71 | 44 |
Check caniuse.com for other browser support:
- WASM SIMD - Indicates Full Support
- WASM bigint - Support for everything except SIMD
- WASM - Basic support (53-bit int and float
number
but no 64-bitbigint
)
Contributing
This is an open source project, and contributions are welcome!
- Bugs: Open an Issue to report a bug or request a feature
- Features: For now, please first open an Issue to discuss any desired / planned contributions
- Full contribution guidelines coming soon
- See scripts in
package.json
for available build commands