npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@arkv/rng

v0.8.2

Published

Fastest, zero-dependency, pseudo-RNG in node and browser environments, with support for cryptographic randomness and seedable PRNG.

Readme

@arkv/rng

Cryptographically correct uniform distributions!

Fastest, seedable, zero dependency, isomorphic pseudo-random number generator powered by Rust + WebAssembly. Works identically in Node.js, Bun, and the browser — no native compilation required.

Five PRNG algorithms are available (pcg64, xoroshiro128+, xorshift128+, mersenne, lcg32), all seedable with a number, bigint, or string. String seeds are hashed to a u64 via FNV-1a 64-bit inside the WebAssembly layer — no JavaScript hashing.

@arkv/rng is heavily optimized for zero-overhead boundary crossing. Calling .int() repeatedly inside a JavaScript loop incurs microscopic overhead as execution passes between JS and WebAssembly.

To bypass this for large datasets, use the batch methods: .ints(N), .floats(N), .ranges(min, max, N), and .shuffle(array). These methods compute the entire sequence natively in Rust and return the results as a single typed array, eliminating per-value boundary crossings entirely.

Table of Contents

Install

bun add @arkv/rng
# or
npm install @arkv/rng

Usage

Basic

const rng = new Rng(); // system entropy

rng.int();         // u32 in [0, 2^32)
rng.float();       // float in [0, 1)
rng.range(1, 10);  // integer in [1, 10)
rng.bool();        // boolean (50/50)

rng.free(); // release Wasm memory

Seeded (deterministic)

Pass any number, bigint, or string seed for reproducible sequences:

// number seed
const rng1 = new Rng(12345);
console.log(rng1.int()); // always the same value

// bigint seed
const rng2 = new Rng(12345n);
console.log(rng2.int()); // always the same value

// string seed — hashed to u64 via FNV-1a inside WebAssembly
const rng3 = new Rng('hello.');
console.log(rng3.int()); // always the same value
console.log(rng3.int()); // always the next value in the sequence

// note: 'hello.' and 'world.' produce different sequences
// note: '42' and 42 produce different sequences

Choosing an algorithm

The default algorithm is pcg64. Pass a second argument to select another:

const rng = new Rng('my seed', 'xoroshiro128+');

// Available algorithms:
// 'pcg64'         — default, excellent statistical quality
// 'xoroshiro128+' — fast, 128-bit state
// 'xorshift128+'  — fast, minimal state
// 'mersenne'      — MT19937-64, 623-dimensional equidistribution
// 'lcg32'         — simplest, highest throughput

Array utilities

const rng = new Rng();

rng.ints(100);                    // Uint32Array of 100 random integers
rng.floats(100);                  // Float64Array of 100 random floats in [0, 1)
rng.ranges(1, 100, 50);           // Uint32Array of 50 integers in [1, 100)
rng.pick([1, 2, 3, 4, 5]);        // Returns a random element
rng.shuffle([1, 2, 3, 4, 5]);     // Returns a new, randomly shuffled array
rng.bool(0.8);                    // Returns true 80% of the time

API

new Rng(seed?, algorithm?)

| Param | Type | Description | |-------|------|-------------| | seed | number \| bigint \| string | Optional seed for deterministic output. Omit for system entropy. String seeds are hashed via FNV-1a 64-bit in Rust. | | algorithm | RngAlgorithm | PRNG backend. Default: 'pcg64'. |

type RngAlgorithm = 'pcg64' | 'xoroshiro128+' | 'xorshift128+' | 'mersenne' | 'lcg32';

| Method | Return | Description | |--------|--------|-------------| | int() | number | Random u32 in [0, 2^32) | | ints(length) | Uint32Array | Batched array of random u32 integers | | bigInt() | bigint | Random u64 as a BigInt in [0, 2^64) | | intStream(bufferSize?) | () => number | High-throughput buffered integer closure (default buffer: 256) | | float() | number | Random float in [0, 1) with 53-bit precision | | floats(length) | Float64Array | Batched array of random floats in [0, 1) | | range(min, max) | number | Random integer in [min, max) | | ranges(min, max, length) | Uint32Array | Batched array of random integers in [min, max) | | bool(probability?) | boolean | Random boolean; default 0.5 | | pick(array) | T | Random element from a non-empty array | | shuffle(array) | T[] | New randomly ordered copy of the array | | free() | void | Release Wasm memory |

License

MIT


Development

Prerequisites

| Tool | Install | |------|---------| | Bun >= 1.0 | curl -fsSL https://bun.sh/install \| bash | | Rust stable | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \| sh | | wasm-pack >= 0.13 | cargo install wasm-pack |

Run the setup script to install Rust + wasm-pack in one step:

bash packages/rng/setup.sh

Build

bun install

cd packages/rng

# Full build: Rust -> Wasm -> TypeScript -> dist/
bun run build

# Wasm only (run once before tests, or after changing Rust)
bun run build:wasm

# TypeScript only (after Wasm is already built)
bun run build:ts

Test

# Wasm must be built first
bun run build:wasm

bun test
bun test --coverage

Benchmark

Run bun run build:wasm && bun run bench to reproduce.

Compared against: seedrandom (all 7 algorithm variants), pure-rand (all 4 algorithms), random-js (Mersenne Twister).

1 · Sequential u32 Integer (N=100,000)

| Library | ms | ops/sec | vs fastest | |:--------|---:|-------:|----------:| | @arkv/rng · pcg64 | 1.49 | 67,055,499 | 4.01x | | @arkv/rng · xoroshiro128+ | 0.96 | 104,698,009 | 2.57x | | @arkv/rng · xorshift128+ | 1.31 | 76,163,783 | 3.53x | | @arkv/rng · mersenne | 2.01 | 49,729,051 | 5.41x | | @arkv/rng · lcg32 | 1.15 | 86,741,629 | 3.10x | | seedrandom · default/ARC4 | 2.77 | 36,061,207 | 7.46x | | seedrandom · alea | 0.61 | 165,099,596 | 1.63x | | seedrandom · xor128 | 0.37 | 268,945,170 | fastest | | seedrandom · tychei | 1.04 | 95,853,566 | 2.81x | | seedrandom · xorwow | 2.00 | 50,008,351 | 5.38x | | seedrandom · xor4096 | 2.13 | 46,994,093 | 5.72x | | seedrandom · xorshift7 | 0.50 | 199,527,519 | 1.35x | | pure-rand · xoroshiro128+ (uniform) | 1.34 | 74,835,120 | 3.59x | | pure-rand · xorshift128+ (uniform) | 1.40 | 71,432,092 | 3.77x | | pure-rand · mersenne (uniform) | 1.24 | 80,884,619 | 3.33x | | pure-rand · congruential32 (uniform) | 0.85 | 117,371,305 | 2.29x | | random-js · MersenneTwister | 2.49 | 40,179,361 | 6.69x |

2 · Batched u32 Array (100 k elements) (N=100,000)

| Library | ms | ops/sec | vs fastest | |:--------|---:|-------:|----------:| | @arkv/rng · pcg64 [native batch] | 0.51 | 195,433,119 | 5.16x | | @arkv/rng · xoroshiro128+ [native batch] | 0.26 | 383,756,361 | 2.63x | | @arkv/rng · xorshift128+ [native batch] | 0.58 | 173,098,727 | 5.83x | | @arkv/rng · mersenne [native batch] | 0.93 | 107,157,362 | 9.41x | | @arkv/rng · lcg32 [native batch] | 0.10 | 1,008,766,178 | fastest | | pure-rand · xoroshiro128+ loop | 2.14 | 46,623,568 | 21.64x | | pure-rand · xorshift128+ loop | 2.42 | 41,285,805 | 24.43x | | pure-rand · mersenne loop | 2.46 | 40,607,983 | 24.84x | | pure-rand · congruential32 loop | 2.24 | 44,656,254 | 22.59x | | random-js loop | 1.63 | 61,358,238 | 16.44x |

3 · Float [0, 1) — 53-bit precision

`@arkv/rng` and `pure-rand` generate high-resolution floats with full **53-bit precision** (IEEE 754 standard). `seedrandom` generates lower-resolution floats with only **32-bit precision**. This quality difference explains pure-rand's slower single-call numbers — it rolls two 32-bit integers per float, whereas seedrandom rolls just one.  (N=100,000)

| Library | ms | ops/sec | vs fastest | |:--------|---:|-------:|----------:| | @arkv/rng · pcg64 [batch] | 0.55 | 182,867,174 | 3.01x | | @arkv/rng · xoroshiro128+ [batch] | 0.87 | 115,407,209 | 4.77x | | @arkv/rng · xorshift128+ [batch] | 0.30 | 330,063,504 | 1.67x | | @arkv/rng · mersenne [batch] | 0.87 | 115,060,631 | 4.78x | | @arkv/rng · lcg32 [batch] | 0.18 | 549,958,203 | fastest | | @arkv/rng · pcg64 [single] | 0.86 | 116,756,004 | 4.71x | | seedrandom · default/ARC4 | 2.77 | 36,127,925 | 15.22x | | seedrandom · alea | 0.65 | 154,310,272 | 3.56x | | seedrandom · xor128 | 0.37 | 267,106,145 | 2.06x | | seedrandom · tychei | 0.58 | 173,306,020 | 3.17x | | seedrandom · xorwow | 0.86 | 116,921,185 | 4.70x | | seedrandom · xor4096 | 0.86 | 116,428,396 | 4.72x | | seedrandom · xorshift7 | 0.50 | 198,261,642 | 2.77x | | pure-rand · xoroshiro128+ | 1.45 | 69,015,875 | 7.97x | | pure-rand · xorshift128+ | 1.53 | 65,271,510 | 8.43x | | pure-rand · mersenne | 2.16 | 46,383,523 | 11.86x | | pure-rand · congruential32 | 1.54 | 65,118,030 | 8.45x | | random-js · Random.real(0, 1) | 1.95 | 51,362,493 | 10.71x |

4 · Bounded Range [1, 1000) — uniform distribution

`@arkv/rng` uses **unbiased rejection sampling** (via the `rand` crate), which guarantees a perfectly uniform distribution. `seedrandom + Math.floor()` uses biased float multiplication — faster but mathematically incorrect (modulo bias). `pure-rand` also uses unbiased sampling, explaining its slower numbers. `@arkv/rng` produces cryptographically correct uniform integers faster than `seedrandom` produces biased ones.  (N=100,000)

| Library | ms | ops/sec | vs fastest | |:--------|---:|-------:|----------:| | @arkv/rng · pcg64 ranges [batch] | 0.68 | 146,217,001 | 3.06x | | @arkv/rng · xoroshiro128+ ranges [batch] | 0.41 | 246,783,181 | 1.81x | | @arkv/rng · xorshift128+ ranges [batch] | 0.41 | 246,673,606 | 1.82x | | @arkv/rng · mersenne ranges [batch] | 0.53 | 187,964,625 | 2.38x | | @arkv/rng · lcg32 ranges [batch] | 0.22 | 447,906,262 | fastest | | @arkv/rng · pcg64 range() [single] | 0.99 | 100,858,508 | 4.44x | | seedrandom · default/ARC4 + floor | 3.02 | 33,057,917 | 13.55x | | seedrandom · alea + floor | 0.68 | 147,248,081 | 3.04x | | seedrandom · xor128 + floor | 0.44 | 229,293,112 | 1.95x | | seedrandom · tychei + floor | 1.06 | 94,392,251 | 4.75x | | seedrandom · xorwow + floor | 1.89 | 52,905,154 | 8.47x | | seedrandom · xor4096 + floor | 2.09 | 47,753,964 | 9.38x | | seedrandom · xorshift7 + floor | 0.57 | 174,900,088 | 2.56x | | pure-rand · xoroshiro128+ uniformInt | 1.03 | 96,852,488 | 4.62x | | pure-rand · xorshift128+ uniformInt | 1.06 | 94,179,872 | 4.76x | | pure-rand · mersenne uniformInt | 1.28 | 77,834,337 | 5.75x | | pure-rand · congruential32 uniformInt | 0.90 | 111,187,954 | 4.03x | | random-js · Random.integer(1, 999) | 8.95 | 11,170,848 | 40.10x |

5 · Array Shuffle (100 k elements) (N=100,000)

| Library | ms | ops/sec | vs fastest | |:--------|---:|-------:|----------:| | @arkv/rng · pcg64 shuffle() | 2.36 | 42,350,827 | 1.65x | | @arkv/rng · xoroshiro128+ shuffle() | 1.44 | 69,498,885 | 1.01x | | @arkv/rng · xorshift128+ shuffle() | 1.94 | 51,463,731 | 1.36x | | @arkv/rng · mersenne shuffle() | 2.12 | 47,137,415 | 1.48x | | @arkv/rng · lcg32 shuffle() | 1.43 | 69,870,900 | fastest | | seedrandom · default/ARC4 Fisher-Yates | 7.88 | 12,687,565 | 5.51x | | seedrandom · alea Fisher-Yates | 5.66 | 17,656,689 | 3.96x | | seedrandom · xor128 Fisher-Yates | 6.26 | 15,973,675 | 4.37x | | seedrandom · tychei Fisher-Yates | 8.45 | 11,833,748 | 5.90x | | seedrandom · xorwow Fisher-Yates | 7.85 | 12,738,832 | 5.48x | | seedrandom · xor4096 Fisher-Yates | 8.84 | 11,311,998 | 6.18x | | seedrandom · xorshift7 Fisher-Yates | 4.58 | 21,827,694 | 3.20x | | pure-rand · xoroshiro128+ Fisher-Yates | 8.81 | 11,349,010 | 6.16x | | pure-rand · xorshift128+ Fisher-Yates | 8.01 | 12,484,014 | 5.60x | | pure-rand · mersenne Fisher-Yates | 8.67 | 11,536,395 | 6.06x | | pure-rand · congruential32 Fisher-Yates | 8.14 | 12,279,030 | 5.69x | | random-js · Random.shuffle() [in-place] | 3.07 | 32,554,574 | 2.15x |

6 · String-seeded Float [0, 1) (N=100,000)

| Library | ms | ops/sec | vs fastest | |:--------|---:|-------:|----------:| | @arkv/rng · pcg64 [string seed] | 0.91 | 110,284,346 | 2.10x | | @arkv/rng · xoroshiro128+ [string seed] | 0.85 | 118,259,644 | 1.96x | | @arkv/rng · xorshift128+ [string seed] | 1.03 | 97,139,438 | 2.38x | | @arkv/rng · mersenne [string seed] | 1.45 | 68,875,646 | 3.36x | | @arkv/rng · lcg32 [string seed] | 1.68 | 59,598,556 | 3.88x | | seedrandom · default/ARC4 [string seed] | 2.86 | 34,948,502 | 6.62x | | seedrandom · alea [string seed] | 0.72 | 138,649,141 | 1.67x | | seedrandom · xor128 [string seed] | 0.43 | 231,398,992 | fastest | | seedrandom · tychei [string seed] | 0.61 | 163,638,000 | 1.41x | | seedrandom · xorwow [string seed] | 0.80 | 124,589,478 | 1.86x | | seedrandom · xor4096 [string seed] | 0.85 | 117,815,188 | 1.96x | | seedrandom · xorshift7 [string seed] | 0.51 | 197,583,944 | 1.17x |

7 · intStream() — Buffered Single Integer (N=100,000)

| Library | ms | ops/sec | vs fastest | |:--------|---:|-------:|----------:| | @arkv/rng · pcg64 intStream() | 1.35 | 73,939,832 | 3.57x | | @arkv/rng · xoroshiro128+ intStream() | 1.12 | 89,029,833 | 2.97x | | @arkv/rng · lcg32 intStream() | 0.87 | 115,133,492 | 2.29x | | seedrandom · default/ARC4 | 2.82 | 35,489,523 | 7.44x | | seedrandom · alea | 0.66 | 150,789,761 | 1.75x | | seedrandom · xor128 | 0.38 | 264,212,659 | fastest | | seedrandom · tychei | 1.05 | 95,109,652 | 2.78x | | seedrandom · xorwow | 2.01 | 49,842,572 | 5.30x | | seedrandom · xor4096 | 2.14 | 46,680,176 | 5.66x | | seedrandom · xorshift7 | 0.53 | 188,973,759 | 1.40x |

8 · Native 64-bit BigInt

`@arkv/rng` generates a 64-bit integer natively in Rust in a single CPU operation. Pure-JS libraries must roll two 32-bit values and stitch them via BigInt arithmetic — consuming twice the RNG calls and adding JS BigInt overhead.  (N=100,000)

| Library | ms | ops/sec | vs fastest | |:--------|---:|-------:|----------:| | @arkv/rng · pcg64 bigInt() | 2.67 | 37,501,064 | fastest | | @arkv/rng · xoroshiro128+ bigInt() | 5.13 | 19,483,333 | 1.92x | | @arkv/rng · xorshift128+ bigInt() | 2.99 | 33,476,120 | 1.12x | | @arkv/rng · mersenne bigInt() | 3.20 | 31,266,943 | 1.20x | | @arkv/rng · lcg32 bigInt() | 2.93 | 34,082,896 | 1.10x | | seedrandom · default/ARC4 (2×32-bit + BigInt) | 11.86 | 8,431,821 | 4.45x | | seedrandom · alea (2×32-bit + BigInt) | 5.39 | 18,536,667 | 2.02x | | seedrandom · xor128 (2×32-bit + BigInt) | 9.05 | 11,052,370 | 3.39x | | seedrandom · tychei (2×32-bit + BigInt) | 12.19 | 8,204,463 | 4.57x | | seedrandom · xorwow (2×32-bit + BigInt) | 11.75 | 8,508,427 | 4.41x | | seedrandom · xor4096 (2×32-bit + BigInt) | 8.62 | 11,597,512 | 3.23x | | seedrandom · xorshift7 (2×32-bit + BigInt) | 12.04 | 8,307,907 | 4.51x |