High-performance deterministic and secure random number generators for web and node applications.
The package provides several backend options including low-overhead JavaScript generators, fast WebAssembly (WASM) via the Rust/rand crate and faster WebGPU for large workloads.
Interactive demo · API reference
Install @neutrium/rand with npm or pnpm as shown below. Note Node.js 20 or newer is required (The package is ESM-only).
# Install using pnpm
pnpm add @neutrium/rand
# Install using npm
npm install @neutrium/rand
import { Random } from "@neutrium/rand";
const rng = Random.fast("experiment-42");
const scalar = rng.next();
const integer = rng.next_u32();
const values = rng.float64_array(1_000_000);
const reusable = rng.fill(new Float32Array(1_000_000));
const bounded = rng.integer(100);
const normal = rng.normal(0, 1);
Use the uniform asynchronous create API when the backend may vary:
const rng = await Random.create({
seed: "experiment-42"
});
const values = await rng.float64_array(5_000_000);
await rng.destroy_async();
Installing this package globally with npm install -g @neutrium/rand provides the neutrium-rand command.
Alternatively, you can use the CLI locally using:
npx --package @neutrium/rand neutrium-rand --help
Use -h or --help to see the supported parameters for each of the commands e.g. neutrium-rand generate --help.
pnpm demo.A number of algorithms have been included in @neutrium/rand. These algorithms have been selected because they are best-in-class for specific use cases. They are summarised below:
| Algorithm | Primary use | Security |
|---|---|---|
| Xoshiro128++ | Low-overhead JavaScript scalar calls | Non-cryptographic |
| Xoshiro256++ | Sequential CPU Monte Carlo | Non-cryptographic |
| Philox4x32-10 | Counter-partitioned CPU/GPU work | Non-cryptographic |
| PCG32 | Explicit independent CPU streams | Non-cryptographic |
| ChaCha20 | Deterministic state-inference resistance | Cryptographic primitive |
| System CSPRNG | Fresh unpredictable entropy | Cryptographic |
| SplitMix64 | Internal seed expansion | Non-cryptographic |
A four-word, 128-bit-state generator designed around 32-bit rotates, XORs, and shifts. These operations map efficiently to JavaScript integer operators, making it the scalar default.
References: Blackman and Vigna, Scrambled Linear Pseudorandom Number Generators; xoshiro128++ reference implementation; rand_xoshiro
A 256-bit-state generator for sequential Monte Carlo workloads. The JavaScript implementation represents 64-bit lanes as 32-bit words to avoid BigInt in the hot path. The WASM bridge uses the rand_xoshiro crate.
References: Blackman and Vigna, Scrambled Linear Pseudorandom Number Generators; xoshiro256++ reference implementation; rand_xoshiro
A counter-based generator transforming a 128-bit counter with a 64-bit key over ten rounds and returning four uint32 words per block. Counter independence makes deterministic CPU workers and WebGPU dispatch straightforward. The TypeScript, Rust/WASM, and WGSL implementations are exact-parity tested.
References: Salmon et al., Parallel Random Numbers: As Easy as 1, 2, 3; Random123 Reference Implementations.
The 20-round ChaCha stream cipher is used as a deterministic random word stream. SHA-256 from @noble/hashes derives the key and nonce material from the public seed API. Security depends on the entropy and secrecy of the seed.
References: Bernstein, ChaCha, a variant of Salsa20; RFC 8439.
Uses globalThis.crypto.getRandomValues backed by the platform’s secure random source. Requests are cached for scalar efficiency and chunked to the Web Crypto request limit.
Reference: Web Cryptography API.
A 64-bit mixer used internally to expand seeds into the generator state. It is exported only for advanced seeding workflows and is not a security generator.
References: Steele, Lea, and Flood, Fast Splittable Pseudorandom Number Generators; SplitMix64 reference implementation.
The XSH-RR 64/32 variant combines a 64-bit linear-congruential transition with an output permutation. The odd increment selects one of 2^63 independent streams. Numeric stream IDs must be non-negative integers below 2^63; strings and byte arrays are hashed into that stream space. The JavaScript implementation uses portable BigInt arithmetic; the WASM bridge uses rand_pcg.
References: O'Neill, PCG A Family of Simple Fast Space-Efficient Statistically Good Algorithms for Random Number Generation; PCG reference implementation; rand_pcg
Choosing the best backend can be complicated, as performance depends on more than just generator throughput. Initialisation, allocation, worker coordination, GPU submission, readback, and downstream calculation all impact performance. This section provides guidance on selecting the best backend; however, for performance-critical applications, it is recommended that you benchmark on your target hardware.
This package includes implementations that will run on the CPU or the GPU. To reduce the core dependencies, the WebGPU implementations are maintained on a separate subpath:
import { Random } from "@neutrium/rand";
// or
import { Random } from "@neutrium/rand/webgpu";
Both paths use the same high-level type names: RandomCapabilities, RandomCreateOptions, RandomGenerator, Recommendation, and RecommendationOptions. The WebGPU subpath widens those types with its optional backend, output-location, and timestamp fields. Recommendation is asynchronous on both paths, so switching imports does not change the calling
convention or require renamed types.
If you have the potential to run on the GPU and don’t mind the additional dependencies, favour importing @neutrium/rand/webgpu.
To receive a deterministic recommendation from the library:
import { Random } from "@neutrium/rand/webgpu";
const recommendation = await Random.recommend({
call_pattern: "bulk",
count: 8_000_000,
security: "none",
output_location: "gpu",
workers: 1
});
console.log(recommendation);
// {
// algorithm: "philox4x32-10",
// backend: "webgpu",
// confidence: "default",
// reason: "...",
// alternatives: [...]
// }
If confidence: "benchmark-required" is returned, the backend recommendation is highly dependent on transfer or deployment details.
Recommendation objects intentionally contain valid algorithm and backend creation options. Pass them directly to the corresponding creation API, adding only stream state, such as the seed and stream identifier:
const recommendation = await Random.recommend({ count: 100_000 });
const rng = await Random.create({
...recommendation,
seed: "experiment-42"
});
The default crossover values — 16,384 for WASM and 1,000,000 for WebGPU — are conservative routing hints, not performance guarantees. Calibrate them with pnpm benchmark on the deployment target to find the best crossover points.
The following list is some general guidelines on selecting which algorithm and backend will be best for you.
Random.secure().Random.secure(seed).Random.scalar(seed).A general selection matrix is presented below. Note that this does not consider the requirements for cryptographically secure random numbers.
| Environment | Workload | Starting choice | Why |
|---|---|---|---|
| CPU only | One or scattered values | Xoshiro128++ JS | Lowest API/setup overhead |
| CPU only | Small sequential batch | Xoshiro256++ JS | No WASM boundary cost |
| CPU only | Large sequential batch | Xoshiro256++ WASM | Bulk transfer amortises setup |
| Multiple CPU workers | Reproducible partitions | Philox | Counter and stream partitioning |
| Multiple CPU workers | Long independent streams | PCG32 | Explicit odd-increment streams |
| GPU available | Small batch | JavaScript/WASM | GPU submission dominates |
| GPU available | Large batch returned to CPU | Benchmark Philox WebGPU and WASM | Readback is hardware-dependent |
| GPU available | Large GPU-resident simulation | Philox WebGPU | Avoids the largest transfer |
A summary of the high level API is provided below with full documentation available in the API reference. To build and serve the same site locally, run pnpm run docs followed by pnpm docs:preview.
import { Random } from "@neutrium/rand";
Random.fast(seed); // Xoshiro256++
Random.scalar(seed); // Xoshiro128++
Random.parallel(seed, stream, counter); // Philox4x32-10
Random.stream(seed, stream); // PCG32
Random.secure(); // System CSPRNG
Random.secure(seed); // Deterministic ChaCha20
await Random.create(options); // Uniform async generator factory
await Random.capabilities(); // Probes runtime capabilities
await Random.recommend(workload); // Recommends a algorithm and backend
Applications that may use WebGPU replace only the import. The method names and calling conventions remain the same:
import { Random } from "@neutrium/rand/webgpu";
await Random.capabilities(); // Also reports verified WebGPU availability
await Random.recommend(workload); // Includes WebGPU in workload recommendations
await Random.create(options); // Uniform async API with WebGPU fallback
Seeds may be numbers, bigints, strings, or byte arrays. Equal seed bytes and options produce equal streams across supported platforms and parity-tested backends.
Synchronous generators provide:
next_u32();
next();
next_f64();
fill(target);
uint32_array(count);
float32_array(count);
float64_array(count);
bytes(count);
integer(max_exclusive);
normal(mean, standard_deviation);
exponential(rate);
destroy();
fill(target) always reuses a caller-owned buffer. The typed *_array() methods always allocate a new buffer. Synchronous generators return the array directly; Random.create() from either package path exposes the same allocation names asynchronously.
With backend: "auto", the first non-empty array request supplies the workload count used to choose JavaScript, WASM, or optional WebGPU. The selected backend is then retained so subsequent calls continue the same stream.
Passing an AbortSignal to an array method on a generator returned by Random.create() enables cooperative JavaScript and WASM generation in bounded chunks. Cancellation can consume values generated before the signal is observed, so discard or checkpoint that generator when retrying must reproduce the same stream position.
Parallel reproducibility requires deterministic ownership of streams or counter ranges. A shared seed will create the same sequences of random numbers.
Give each worker a unique persisted stream ID:
const worker_id = 7;
const rng = Random.parallel("portfolio-2026", worker_id);
const samples = rng.fill(new Float64Array(2_000_000));
Do not derive stream IDs from transient process IDs or queue order. A job coordinator should assign them.
Philox emits four uint32 words per counter block. Allocate non-overlapping block ranges when jobs may generate different lengths:
const words_per_job = 4_000_000;
const blocks_per_job = Math.ceil(words_per_job / 4);
const starting_counter = job_id * blocks_per_job;
const rng = Random.parallel(seed, stream_id, starting_counter);
Validate that the largest job ID cannot exceed the supported counter space.
Use Random.stream(seed, stream_id) when the application already models long-lived independent CPU streams. Philox is generally easier for arbitrary job partitioning because a counter directly identifies work.
Philox is available through WebGPU because its counter-based blocks can be computed independently. WebGPU is useful when a large generated buffer is consumed by subsequent GPU work.
WebGPU is an optional API surface. It is not loaded or included in the root @neutrium/rand declarations. Install the WebGPU declarations and import the dedicated subpath only in projects that use it:
pnpm add @neutrium/rand
pnpm add -D @webgpu/types
Add "@webgpu/types" to compilerOptions.types, then import the subpath:
import { Random } from "@neutrium/rand/webgpu";
const rng = await Random.create({
algorithm: "philox4x32-10",
backend: "auto",
seed,
stream: 7
});
const values = await rng.uint32_array(count);
await rng.destroy_async();
This includes allocation, dispatch, command submission, GPU-to-CPU copying, and mapping. Compare it against WASM on the target device. A discrete GPU can have excellent compute throughput while losing the complete workload benchmark to readback latency.
const rng = await Random.create({
algorithm: "philox4x32-10",
backend: "webgpu",
seed,
stream: 7
});
const generated = await rng.generate_buffer({
count,
usage: GPUBufferUsage.COPY_SRC
});
// Create the consumer bind group on generated.device.
// Bind generated.buffer as read-only storage.
// Submit the consumer work before destroying either object.
generated.buffer.destroy();
await rng.destroy_async();
The requested count must be a positive multiple of four, and the resulting buffer must fit maxBufferSize, maxStorageBufferBindingSize, and dispatch limits. Typed-array requests are chunked; GPU-resident requests deliberately fail rather than returning several buffers with an ambiguous consumer contract.
Several tests are included in @neutrium/rand. These are listed below, along with any requirements in addition to running pnpm bootstrap.
| Command | Additional setup | Purpose | Notes |
|---|---|---|---|
pnpm test |
None after bootstrap | Deterministic, property, parity, and distribution tests | Node correctness, parity, properties, distribution smoke tests, and mocked WebGPU lifecycle tests |
pnpm test:coverage |
None after bootstrap | Enforced coverage thresholds | Runs the Node suite with enforced coverage thresholds |
pnpm test:rust |
None after bootstrap | Native Rust bridge tests | Uses the toolchain pinned in rust-toolchain.toml |
pnpm test:package |
tar on PATH |
Packed ESM, declarations, export restrictions, and file layout | Rebuilds and inspects the packed ESM-only package |
pnpm test:statistical |
None after bootstrap | Deterministic distribution smoke tests | Fast deterministic distribution checks; it does not invoke PractRand |
pnpm test:browser |
Playwright Chromium, Firefox, and WebKit | Tests Chromium, Firefox, and WebKit availability | Builds first, then runs the browser suite in all three engines |
pnpm statistical:practrand |
PractRand RNG_test executable on PATH |
Statistical testing using the PractRand battery | Streams a large battery input; PractRand is not a pnpm dependency |
Generated test artifacts are grouped under reports/: coverage output in reports/coverage, Playwright's HTML report in reports/playwright, and individual test-run artifacts and statistical reports in reports/tests.
External statistical test suites are not installed by default and require additional setup.
PractRand is a C++ test suite and can be downloaded from the
official PractRand site. Follow installation.txt to build the RNG_test command and place that executable on PATH.
Use a small local smoke run before starting the default multi-gigabyte battery:
PRACTRAND_WORDS=1048576 \
PRACTRAND_SEEDS=smoke \
pnpm statistical:practrand
For a release-scale run:
PRACTRAND_ALGORITHM=xoshiro256++ \
PRACTRAND_WORDS=268435456 \
PRACTRAND_SEEDS=run-a,run-b,run-c \
PRACTRAND_REPORT=reports/tests/practrand.json \
pnpm statistical:practrand
Install and build the battery separately, then generate a reproducible binary stream for the adapter or harness you use with it:
pnpm stream philox4x32-10 ./philox.bin 1000000000 test-seed
Exact backend parity means one algorithm stream can receive the expensive statistical battery while every backend receives parity tests.
This library includes benchmarks to compare the performance of each algorithm on your hardware. The benchmarks measure generator speed, setup costs, memory allocation patterns, backend initialisation, and end-to-end workload behaviour with the JavaScript, WASM, worker-based, and WebGPU backends, where supported.
Running pnpm benchmark builds the package and then runs the main Node benchmark harness. The benchmark results can be used to understand:
All benchmarks run adaptively for a minimum duration of 250ms (by default). The reported values are the median and interquartile range, with raw sample rates being saved in the /reports/benchmarks/ folder.
There are several benchmarks you can run to understand performance with different configurations:
pnpm benchmark measures the generator performance on CPU using javascript and wasmpnpm benchmark:scenarios measures the performance in realistic situations by determining the value of π through a Monte Carlo simulationpnpm benchmark:workers measures the cost and scaling of a consistent workload across various numbers of node worker threadspnpm benchmark:web-workers measures the cost and scaling of a consistent workload across various numbers of web worker threadspnpm benchmark:webgpu is the dedicated browser-backed WebGPU comparison.pnpm benchmark:all runs all aforementioned benchmarksSeveral benchmark parameters can be set, as shown below:
BENCH_MIN_SAMPLE_MS=500 \
BENCH_SAMPLES=15 \
pnpm benchmark
The full list of available benchmark parameters are listed below:
| Parameter | Description | Scope | Default |
|---|---|---|---|
BENCH_JSON |
Sets the output file for the raw report | benchmark | reports/benchmarks/main.json |
BENCH_MIN_SAMPLE_MS |
How long each sample runs in milliseconds | all | 250 |
BENCH_SIZES |
The array lengths tested | all | 1,64,1024,16384,262144,1048576 |
BENCH_SAMPLES |
How many samples are collected | all | 10 |
BENCH_WORKERS |
Number of workers to use in each simulation | workers, web-workers | 1, 2, 4, 8 |
BENCH_WORKER_NUMBERS |
Total number of samples to run for each simulation | workers, web-workers | 20_000_000 |
BENCH_WEB_WORKER_JSON |
Sets output file for the raw report | web-worker | reports/benchmarks/web-workers.json |
BENCH_WEB_WORKER_CHANNEL |
Sets the chromium executable used by Playwright | web-worker | chromium |
BENCH_WEB_WORKER_HEADED |
Sets whether benchmark is run headed | web-worker | 0 |
BENCH_WORKER_JSON |
Sets the output file for the raw report | workers | reports/benchmarks/workers.json |
BENCH_SCENARIO_JSON |
Sets the output file for the raw report | scenario | reports/benchmarks/scenarios.json |
BENCH_WEBGPU_JSON |
Sets output file for the raw report | webgpu | reports/benchmarks/webgpu.json |
Node cannot assume a GPU-backed navigator.gpu, so this benchmark launches Chromium and runs an isolated WebGPU workload there.
For a quick hardware check, run the smoke test:
pnpm benchmark:webgpu:smoke
If headless Chromium cannot access the GPU for webgpu tests, retry with:
BENCH_WEBGPU_HEADED=1 pnpm benchmark:webgpu
The benchmark rejects software fallbacks such as SwiftShader and llvmpipe by default. Set BENCH_WEBGPU_ALLOW_FALLBACK=1 when you intentionally want to measure software WebGPU.
To compare two benchmark runs and check for regressions, generate separate JSON files for each benchmark:
BENCH_JSON=reports/benchmarks/baseline.json pnpm benchmark
BENCH_JSON=reports/benchmarks/candidate.json pnpm benchmark
A regression can then be run using pnpm benchmark:compare, optionally setting a regression threshold:
BENCH_REGRESSION_PERCENT=10 \
pnpm benchmark:compare \
reports/benchmarks/baseline.json \
reports/benchmarks/candidate.json
If the performance percentage change for any benchmark exceeds the threshold, benchmark:compare will exit with a non-zero return value.
The build requires:
packageManager in package.json.After cloning the repository, run:
pnpm run bootstrap
This command verifies Node and pnpm, installs the Rust toolchain, profile, and targets declared by rust-toolchain.toml, installs pnpm dependencies, and builds the library.
If rustup is not installed, pnpm run bootstrap stops with an installation link rather than failing later in the build. Restart the terminal after installing rustup so its commands are available on PATH.
For browser tests and browser-backed benchmarks, install Playwright's browsers separately because they are a large optional download:
pnpm run setup:browsers
Run the complete non-browser verification suite and browser tests with:
pnpm check
pnpm test:browser
pnpm benchmark:smoke
This project is licensed under the MIT License - see the LICENSE file for details. The published bundles also contain third-party software. Its attribution and license terms are recorded in THIRD_PARTY_NOTICES.md, and the machine-readable component inventory is published as sbom.cdx.json.
You are free to:
Under the following conditions:
This plugin is provided "as is", without warranty of any kind. Use at your own risk.