diff --git a/random/_pcg32.ts b/random/_pcg32.ts index b963e6ab0712..1e725e12f887 100644 --- a/random/_pcg32.ts +++ b/random/_pcg32.ts @@ -2,48 +2,45 @@ // Based on Rust `rand` crate (https://github.com/rust-random/rand). Apache-2.0 + MIT license. /** Multiplier for the PCG32 algorithm. */ -const MUL: bigint = 6364136223846793005n; +const MUL = 6364136223846793005n; /** Initial increment for the PCG32 algorithm. Only used during seeding. */ -const INC: bigint = 11634580027462260723n; +const INC = 11634580027462260723n; // Constants are for 64-bit state, 32-bit output const ROTATE = 59n; // 64 - 5 const XSHIFT = 18n; // (5 + 32) / 2 const SPARE = 27n; // 64 - 32 - 5 -/** - * Internal state for the PCG32 algorithm. - * `state` prop is mutated by each step, whereas `inc` prop remains constant. - */ -type PcgMutableState = { - state: bigint; - inc: bigint; -}; +const b4 = new Uint8Array(4); +const dv4 = new DataView(b4.buffer); -/** - * Modified from https://github.com/rust-random/rand/blob/f7bbccaedf6c63b02855b90b003c9b1a4d1fd1cb/rand_pcg/src/pcg64.rs#L129-L135 - */ -export function fromSeed(seed: Uint8Array) { - const d = new DataView(seed.buffer); - return fromStateIncr(d.getBigUint64(0, true), d.getBigUint64(8, true) | 1n); -} +abstract class Prng32 { + /** Generates a pseudo-random 32-bit unsigned integer. */ + abstract nextUint32(): number; -/** - * Mutates `pcg` by advancing `pcg.state`. - */ -function step(pgc: PcgMutableState) { - pgc.state = BigInt.asUintN(64, pgc.state * MUL + (pgc.inc | 1n)); -} + /** + * Mutates the provided `Uint8Array` with pseudo-random values. + * @returns The same `Uint8Array`, now populated with random values. + */ + getRandomValues(bytes: T): T { + const { buffer, byteLength, byteOffset } = bytes; + const rem = byteLength % 4; + const cutoffLen = byteLength - rem; -/** - * Modified from https://github.com/rust-random/rand/blob/f7bbccaedf6c63b02855b90b003c9b1a4d1fd1cb/rand_pcg/src/pcg64.rs#L99-L105 - */ -function fromStateIncr(state: bigint, inc: bigint): PcgMutableState { - const pcg: PcgMutableState = { state, inc }; - // Move away from initial value - pcg.state = BigInt.asUintN(64, state + inc); - step(pcg); - return pcg; + const dv = new DataView(buffer, byteOffset, byteLength); + for (let i = 0; i < cutoffLen; i += 4) { + dv.setUint32(i, this.nextUint32(), true); + } + + if (rem !== 0) { + dv4.setUint32(0, this.nextUint32(), true); + for (let i = 0; i < rem; ++i) { + dv.setUint8(cutoffLen + i, b4[i]!); + } + } + + return bytes; + } } /** @@ -51,50 +48,82 @@ function fromStateIncr(state: bigint, inc: bigint): PcgMutableState { * function and the seed generation algorithm. * * Modified from https://github.com/rust-random/rand/blob/f7bbccaedf6c63b02855b90b003c9b1a4d1fd1cb/rand_pcg/src/pcg64.rs#L140-L153 - * - * `pcg.state` is internally advanced by the function. - * - * @param pcg The state and increment values to use for the PCG32 algorithm. - * @returns The next pseudo-random 32-bit integer. */ -export function nextU32(pcg: PcgMutableState): number { - const state = pcg.state; - step(pcg); - // Output function XSH RR: xorshift high (bits), followed by a random rotate - const rot = state >> ROTATE; - const xsh = BigInt.asUintN(32, (state >> XSHIFT ^ state) >> SPARE); - return Number(rotateRightU32(xsh, rot)); -} +export class Pcg32 extends Prng32 { + state: bigint; + inc: bigint; -// `n`, `rot`, and return val are all u32 -function rotateRightU32(n: bigint, rot: bigint): bigint { - const left = BigInt.asUintN(32, n << (-rot & 31n)); - const right = n >> rot; - return left | right; -} + constructor(state: bigint, inc: bigint) { + super(); + this.state = state; + this.inc = inc; + } -/** - * Convert a scalar bigint seed to a Uint8Array of the specified length. - * Modified from https://github.com/rust-random/rand/blob/f7bbccaedf6c63b02855b90b003c9b1a4d1fd1cb/rand_core/src/lib.rs#L359-L388 - */ -export function seedFromU64(state: bigint, numBytes: number): Uint8Array { - const seed = new Uint8Array(numBytes); + /** @returns The next pseudo-random 32-bit integer. */ + nextUint32(): number { + const state = this.state; + this.step(); + // Output function XSH RR: xorshift high (bits), followed by a random rotate + const rot = state >> ROTATE; + const xsh = BigInt.asUintN(32, (state >> XSHIFT ^ state) >> SPARE); + return Number(this.#rotateRightUint32(xsh, rot)); + } - const pgc: PcgMutableState = { state: BigInt.asUintN(64, state), inc: INC }; - // We advance the state first (to get away from the input value, - // in case it has low Hamming Weight). - step(pgc); + /** Mutates `pcg` by advancing `pcg.state`. */ + step(): this { + this.state = BigInt.asUintN(64, this.state * MUL + (this.inc | 1n)); + return this; + } - for (let i = 0; i < Math.floor(numBytes / 4); ++i) { - new DataView(seed.buffer).setUint32(i * 4, nextU32(pgc), true); + // `n`, `rot`, and return val are all u32 + #rotateRightUint32(n: bigint, rot: bigint): bigint { + const left = BigInt.asUintN(32, n << (-rot & 31n)); + const right = n >> rot; + return left | right; } - const rem = numBytes % 4; - if (rem) { - const bytes = new Uint8Array(4); - new DataView(bytes.buffer).setUint32(0, nextU32(pgc), true); - seed.set(bytes.subarray(0, rem), numBytes - rem); + /** + * Creates a new `Pcg32` instance with entropy generated from the given + * `seed`, treated as an unsigned 64-bit integer. + */ + static seedFromUint64(seed: bigint): Pcg32 { + return this.#fromSeed(seedBytesFromUint64(seed, new Uint8Array(16))); } - return seed; + /** + * Modified from https://github.com/rust-random/rand/blob/f7bbccaedf6c63b02855b90b003c9b1a4d1fd1cb/rand_pcg/src/pcg64.rs#L129-L135 + */ + static #fromSeed(seed: Uint8Array) { + const d = new DataView(seed.buffer); + return this.#fromStateIncr( + d.getBigUint64(0, true), + d.getBigUint64(8, true) | 1n, + ); + } + + /** + * Modified from https://github.com/rust-random/rand/blob/f7bbccaedf6c63b02855b90b003c9b1a4d1fd1cb/rand_pcg/src/pcg64.rs#L99-L105 + */ + static #fromStateIncr(state: bigint, inc: bigint): Pcg32 { + const pcg = new Pcg32(state, inc); + // Move away from initial value + pcg.state = BigInt.asUintN(64, state + inc); + pcg.step(); + return pcg; + } +} + +/** + * Write entropy generated from a scalar bigint seed into the provided Uint8Array, for use as a seed. + * Modified from https://github.com/rust-random/rand/blob/f7bbccaedf6c63b02855b90b003c9b1a4d1fd1cb/rand_core/src/lib.rs#L359-L388 + */ +export function seedBytesFromUint64( + u64: bigint, + bytes: Uint8Array, +): Uint8Array { + return new Pcg32(BigInt.asUintN(64, u64), INC) + // We advance the state first (to get away from the input value, + // in case it has low Hamming Weight). + .step() + .getRandomValues(bytes); } diff --git a/random/_pcg32_test.ts b/random/_pcg32_test.ts index 949623b6901f..a681f10a52b6 100644 --- a/random/_pcg32_test.ts +++ b/random/_pcg32_test.ts @@ -1,9 +1,10 @@ // Copyright 2018-2025 the Deno authors. MIT license. -import { assertEquals } from "../assert/equals.ts"; -import { fromSeed, nextU32, seedFromU64 } from "./_pcg32.ts"; +import { assert, assertEquals } from "@std/assert"; +import { Pcg32, seedBytesFromUint64 } from "./_pcg32.ts"; +import { nextFloat64 } from "./number_types.ts"; -Deno.test("seedFromU64() generates seeds from bigints", async (t) => { +Deno.test("seedBytesFromUint64() generates seeds from bigints", async (t) => { await t.step("first 10 16-bit seeds are same as rand crate", async (t) => { /** * Expected results obtained by copying the Rust code from @@ -26,7 +27,9 @@ Deno.test("seedFromU64() generates seeds from bigints", async (t) => { for (const [i, expected] of expectedResults.entries()) { await t.step(`With seed ${i}n`, () => { - const actual = Array.from(seedFromU64(BigInt(i), 16)); + const actual = Array.from( + seedBytesFromUint64(BigInt(i), new Uint8Array(16)), + ); assertEquals(actual, expected); }); } @@ -42,7 +45,9 @@ Deno.test("seedFromU64() generates seeds from bigints", async (t) => { const slice = expectedBytes.slice(0, i + 1); await t.step(`With length ${i + 1}`, () => { - const actual = Array.from(seedFromU64(1n, i + 1)); + const actual = Array.from( + seedBytesFromUint64(1n, new Uint8Array(i + 1)), + ); assertEquals(actual, slice); }); } @@ -53,51 +58,62 @@ Deno.test("seedFromU64() generates seeds from bigints", async (t) => { await t.step("wraps bigint input to u64", async (t) => { await t.step("exact multiple of U64_CEIL", () => { - const expected = Array.from(seedFromU64(BigInt(0n), 16)); - const actual = Array.from(seedFromU64(U64_CEIL * 99n, 16)); + const expected = Array.from( + seedBytesFromUint64(BigInt(0n), new Uint8Array(16)), + ); + const actual = Array.from( + seedBytesFromUint64(U64_CEIL * 99n, new Uint8Array(16)), + ); assertEquals(actual, expected); }); await t.step("multiple of U64_CEIL + 1", () => { - const expected = Array.from(seedFromU64(1n, 16)); - const actual = Array.from(seedFromU64(1n + U64_CEIL * 3n, 16)); + const expected = Array.from(seedBytesFromUint64(1n, new Uint8Array(16))); + const actual = Array.from( + seedBytesFromUint64(1n + U64_CEIL * 3n, new Uint8Array(16)), + ); assertEquals(actual, expected); }); await t.step("multiple of U64_CEIL - 1", () => { - const expected = Array.from(seedFromU64(-1n, 16)); - const actual = Array.from(seedFromU64(U64_CEIL - 1n, 16)); + const expected = Array.from(seedBytesFromUint64(-1n, new Uint8Array(16))); + const actual = Array.from( + seedBytesFromUint64(U64_CEIL - 1n, new Uint8Array(16)), + ); assertEquals(actual, expected); }); await t.step("negative multiple of U64_CEIL", () => { - const expected = Array.from(seedFromU64(0n, 16)); - const actual = Array.from(seedFromU64(U64_CEIL * -3n, 16)); + const expected = Array.from(seedBytesFromUint64(0n, new Uint8Array(16))); + const actual = Array.from( + seedBytesFromUint64(U64_CEIL * -3n, new Uint8Array(16)), + ); assertEquals(actual, expected); }); await t.step("negative multiple of U64_CEIL", () => { - const expected = Array.from(seedFromU64(0n, 16)); - const actual = Array.from(seedFromU64(U64_CEIL * -3n, 16)); + const expected = Array.from(seedBytesFromUint64(0n, new Uint8Array(16))); + const actual = Array.from( + seedBytesFromUint64(U64_CEIL * -3n, new Uint8Array(16)), + ); assertEquals(actual, expected); }); }); }); -Deno.test("nextU32() generates random 32-bit integers", async (t) => { +Deno.test("nextUint32() generates random 32-bit integers", () => { /** - * Expected results obtained from the Rust `rand` crate as follows: * ```rs * use rand_pcg::rand_core::{RngCore, SeedableRng}; * use rand_pcg::Lcg64Xsh32; * * let mut rng = Lcg64Xsh32::seed_from_u64(0); * for _ in 0..10 { - * println!("{}", rng.next_u32()); + * println!("{},", rng.next_u32()); * } * ``` */ - const expectedResults = [ + const rustRandSamples = [ 298703107, 4236525527, 336081875, @@ -110,13 +126,112 @@ Deno.test("nextU32() generates random 32-bit integers", async (t) => { 2362354238, ]; - const pgc = fromSeed(seedFromU64(0n, 16)); - const next = () => nextU32(pgc); + const pgc = Pcg32.seedFromUint64(0n); + for (const sample of rustRandSamples) { + assertEquals(pgc.nextUint32(), sample); + } +}); - for (const [i, expected] of expectedResults.entries()) { - await t.step(`#${i + 1} generated uint32`, () => { - const actual = next(); - assertEquals(actual, expected); - }); +Deno.test("getRandomValues() writes bytes", () => { + const pgc = Pcg32.seedFromUint64(0n); + + const a = new Uint8Array(10); + const b = a.subarray(3, 8); + const c = pgc.getRandomValues(b); + + assert(b === c); + assertEquals(Array.from(b), [3, 217, 205, 17, 215]); + assertEquals(Array.from(a), [0, 0, 0, 3, 217, 205, 17, 215, 0, 0]); +}); + +Deno.test("nextFloat64() generates the same random numbers as rust rand crate", () => { + /** + * ```rs + * use rand::prelude::*; + * use rand_pcg::Lcg64Xsh32; + * fn main() -> () { + * let mut rng = Lcg64Xsh32::seed_from_u64(0); + * for _ in 0..10 { + * let val: f64 = rng.random(); + * println!("{val},"); + * } + * } + * ``` + */ + const rustRandSamples = [ + 0.986392965323652, + 0.24601264253217958, + 0.37644842389200484, + 0.6668384108033093, + 0.5500284577750535, + 0.027211583252904847, + 0.4610097964014602, + 0.24912787257622104, + 0.10493815385866834, + 0.4625920669083482, + ]; + + const pgc = Pcg32.seedFromUint64(0n); + for (const sample of rustRandSamples) { + assertEquals(nextFloat64(pgc.getRandomValues.bind(pgc)), sample); } }); + +Deno.test("getRandomValues() can be used to generate the same arbitrary numeric types as rust rand crate", async (t) => { + await t.step("u8", () => { + /** + * ```rs + * use rand::prelude::*; + * use rand_pcg::Lcg64Xsh32; + * fn main() -> () { + * let mut rng = Lcg64Xsh32::seed_from_u64(0); + * for _ in 0..10 { + * let val: u8 = rng.random(); + * println!("{val},"); + * } + * } + * ``` + */ + const rustRandSamples = [3, 215, 211, 62, 155, 133, 142, 14, 192, 62]; + + const pgc = Pcg32.seedFromUint64(0n); + for (const sample of rustRandSamples) { + const b = pgc.getRandomValues(new Uint8Array(1)); + assertEquals(b[0], sample); + } + }); + + await t.step("i64", () => { + /** + * ```rs + * use rand::prelude::*; + * use rand_pcg::Lcg64Xsh32; + * fn main() -> () { + * let mut rng = Lcg64Xsh32::seed_from_u64(0); + * for _ in 0..10 { + * let val: u64 = rng.random(); + * println!("{val}n,"); + * } + * } + * ``` + */ + const rustRandSamples = [ + -251005486276683517n, + 4538132255688111059n, + 6944247732487142299n, + -6145746571101709170n, + -8300509879875978816n, + 501965112106777777n, + 8504129729690683813n, + 4595598107041274030n, + 1935767267798412705n, + 8533317468786625891n, + ]; + + const pgc = Pcg32.seedFromUint64(0n); + for (const sample of rustRandSamples) { + const b = pgc.getRandomValues(new Uint8Array(8)); + assertEquals(new DataView(b.buffer).getBigInt64(0, true), sample); + } + }); +}); diff --git a/random/_types.ts b/random/_types.ts index 10f5c1a26ce6..2c52f69e6cf3 100644 --- a/random/_types.ts +++ b/random/_types.ts @@ -12,6 +12,15 @@ */ export type Prng = typeof Math.random; +/** + * A pseudo-random number generator implementing the same contract as + * `crypto.getRandomValues`, i.e. taking a `Uint8Array` and mutating it by + * filling it with random bytes, returning the mutated `Uint8Array` instance. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + */ +export type ByteGenerator = (bytes: Uint8Array) => Uint8Array; + /** * Options for random number generation. * diff --git a/random/deno.json b/random/deno.json index 711f03a40bb5..c6226b6ce46d 100644 --- a/random/deno.json +++ b/random/deno.json @@ -5,6 +5,7 @@ ".": "./mod.ts", "./between": "./between.ts", "./integer-between": "./integer_between.ts", + "./number-types": "./number_types.ts", "./sample": "./sample.ts", "./seeded": "./seeded.ts", "./shuffle": "./shuffle.ts" diff --git a/random/mod.ts b/random/mod.ts index b78ec5e3d8e4..1860bb38537a 100644 --- a/random/mod.ts +++ b/random/mod.ts @@ -21,6 +21,7 @@ export * from "./between.ts"; export * from "./integer_between.ts"; +export * from "./number_types.ts"; export * from "./sample.ts"; export * from "./seeded.ts"; export * from "./shuffle.ts"; diff --git a/random/number_types.ts b/random/number_types.ts new file mode 100644 index 000000000000..8c9e0b1cf28d --- /dev/null +++ b/random/number_types.ts @@ -0,0 +1,46 @@ +// Copyright 2018-2025 the Deno authors. MIT license. +// This module is browser compatible. +import type { ByteGenerator } from "./_types.ts"; + +const b8 = new Uint8Array(8); +const dv8 = new DataView(b8.buffer); + +// 0x1.0p-53 +const FLOAT_64_MULTIPLIER = 2 ** -53; +// assert(1 / FLOAT_64_MULTIPLIER === Number.MAX_SAFE_INTEGER + 1) + +/** + * Get a float64 in the range `[0, 1)` from a random byte generator. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @param byteGenerator A function that fills a `Uint8Array` with random bytes. + * @returns A float64 in the range `[0, 1)`. + * + * @example With a seeded byte generator + * ```ts + * import { nextFloat64, byteGeneratorSeeded } from "@std/random"; + * import { assertEquals } from "@std/assert"; + * + * const byteGenerator = byteGeneratorSeeded(1n); + * assertEquals(nextFloat64(byteGenerator), 0.49116444173310125); + * assertEquals(nextFloat64(byteGenerator), 0.06903754193160427); + * assertEquals(nextFloat64(byteGenerator), 0.16063206851777034); + * ``` + * + * @example With an arbitrary byte generator + * ```ts + * import { nextFloat64 } from "@std/random"; + * import { assertLess, assertGreaterOrEqual } from "@std/assert"; + * + * const val = nextFloat64(crypto.getRandomValues.bind(crypto)); // example: 0.8928746327842533 + * assertGreaterOrEqual(val, 0); + * assertLess(val, 1); + * ``` + */ +export function nextFloat64(byteGenerator: ByteGenerator): number { + byteGenerator(b8); + const int53 = Number(dv8.getBigUint64(0, true) >> 11n); + // assert(int53 <= Number.MAX_SAFE_INTEGER) + return int53 * FLOAT_64_MULTIPLIER; +} diff --git a/random/number_types_test.ts b/random/number_types_test.ts new file mode 100644 index 000000000000..169a490143ce --- /dev/null +++ b/random/number_types_test.ts @@ -0,0 +1,24 @@ +// Copyright 2018-2025 the Deno authors. MIT license. +import { nextFloat64 } from "./number_types.ts"; +import { type ByteGenerator, byteGeneratorSeeded } from "./seeded.ts"; +import { assertEquals, assertGreaterOrEqual, assertLess } from "@std/assert"; + +Deno.test("nextFloat64() gets floats from a seeded byte generator", () => { + const byteGenerator = byteGeneratorSeeded(1n); + assertEquals(nextFloat64(byteGenerator), 0.49116444173310125); + assertEquals(nextFloat64(byteGenerator), 0.06903754193160427); + assertEquals(nextFloat64(byteGenerator), 0.16063206851777034); +}); + +Deno.test("nextFloat64() gets floats that are always in the [0, 1) range", () => { + const zeroValueGenerator: ByteGenerator = (b) => b.fill(0); + assertEquals(nextFloat64(zeroValueGenerator), 0); + + const maxValueGenerator: ByteGenerator = (b) => b.fill(-1); + assertEquals(nextFloat64(maxValueGenerator), 0.9999999999999999); + + const val = nextFloat64(crypto.getRandomValues.bind(crypto)); + + assertGreaterOrEqual(val, 0); + assertLess(val, 1); +}); diff --git a/random/seeded.ts b/random/seeded.ts index 46238c270f5d..f45e85c35278 100644 --- a/random/seeded.ts +++ b/random/seeded.ts @@ -1,12 +1,13 @@ // Copyright 2018-2025 the Deno authors. MIT license. // This module is browser compatible. -import { fromSeed, nextU32, seedFromU64 } from "./_pcg32.ts"; -import type { Prng } from "./_types.ts"; +import { Pcg32 } from "./_pcg32.ts"; +import type { ByteGenerator, Prng } from "./_types.ts"; +export type { ByteGenerator, Prng } from "./_types.ts"; /** * Creates a pseudo-random number generator that generates random numbers in - * the range `[0, 1)`, based on the given seed. The algorithm used for - * generation is {@link https://www.pcg-random.org/download.html | PCG32}. + * the range `[0, 1)`, based on the given seed, with 32 bits of entropy. + * The algorithm used for generation is {@link https://www.pcg-random.org/download.html | PCG32}. * * @experimental **UNSTABLE**: New API, yet to be vetted. * @@ -27,15 +28,31 @@ import type { Prng } from "./_types.ts"; * ``` */ export function randomSeeded(seed: bigint): Prng { - const pcg = fromSeed(seedFromU64(seed, 16)); - return () => uint32ToFloat64(nextU32(pcg)); + const pcg = Pcg32.seedFromUint64(seed); + return () => pcg.nextUint32() / 2 ** 32; } /** - * Convert a 32-bit unsigned integer to a float64 in the range `[0, 1)`. - * This operation is lossless, i.e. it's always possible to get the original - * value back by multiplying by 2 ** 32. + * Creates a pseudo-random byte generator that populates `Uint8Array`s, + * based on the given seed. The algorithm used for generation is + * {@link https://www.pcg-random.org/download.html | PCG32}. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @param seed The seed used to initialize the random number generator's state. + * @returns A pseudo-random byte generator function, which will generate + * different bytes on each call. + * + * @example Usage + * ```ts + * import { byteGeneratorSeeded } from "@std/random"; + * import { assertEquals } from "@std/assert"; + * + * const byteGenerator = byteGeneratorSeeded(1n); + * assertEquals(byteGenerator(new Uint8Array(5)), new Uint8Array([230, 11, 167, 51, 238])); + * ``` */ -function uint32ToFloat64(u32: number): number { - return u32 / 2 ** 32; +export function byteGeneratorSeeded(seed: bigint): ByteGenerator { + const pcg = Pcg32.seedFromUint64(seed); + return pcg.getRandomValues.bind(pcg); } diff --git a/random/seeded_test.ts b/random/seeded_test.ts index 89a04163ffe1..2ee2c5136cce 100644 --- a/random/seeded_test.ts +++ b/random/seeded_test.ts @@ -1,5 +1,6 @@ // Copyright 2018-2025 the Deno authors. MIT license. -import { randomSeeded } from "./seeded.ts"; +import { nextFloat64 } from "./number_types.ts"; +import { byteGeneratorSeeded, type Prng, randomSeeded } from "./seeded.ts"; import { assertAlmostEquals, assertEquals } from "@std/assert"; Deno.test("randomSeeded() generates random numbers", () => { @@ -10,8 +11,13 @@ Deno.test("randomSeeded() generates random numbers", () => { assertEquals(prng(), 0.7924694607499987); }); -Deno.test("randomSeeded() gives relatively uniform distribution of random numbers", async (t) => { - const prng = randomSeeded(1n); +Deno.test("byteGeneratorSeeded() with nextFloat64() gives relatively uniform distribution of random numbers", async (t) => { + function randomSeeded53Bit(seed: bigint): Prng { + const byteGenerator = byteGeneratorSeeded(seed); + return () => nextFloat64(byteGenerator); + } + + const prng = randomSeeded53Bit(1n); const results = Array.from({ length: 1e4 }, prng); await t.step("all results are in [0, 1)", () => { @@ -61,3 +67,11 @@ Deno.test("randomSeeded() gives relatively uniform distribution of random number }, ); }); + +Deno.test("byteGeneratorSeeded() generates bytes", () => { + const prng = byteGeneratorSeeded(1n); + assertEquals( + prng(new Uint8Array(5)), + new Uint8Array([230, 11, 167, 51, 238]), + ); +});