1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
import Prando from 'prando';
import { none } from 'fp-ts/lib/Option.js';
import * as G from './gen.js';
import { test, expect } from 'vitest';
import { chiSquare, computeChiSquaredPValue, computeCramersV } from './stats.js';
test('can generate and shrink bytes', async () => {
const prng = new Prando(Math.random() * 1000);
const gen = new G.BytesGen(prng);
const bytes: number[][] = [];
for (let i = 1; i < 10; i++) {
bytes.push(gen.generate(i));
}
console.log('Generated bytes:', bytes);
const replay = gen.replay();
for (let i = 1; i < 10; i++) {
const replayed = replay.generate(i);
expect(replayed, `Replaying at ${i}`).toEqual(bytes[i - 1]);
}
});
test('can generate an arbitrary integer', () => {
const prng = new Prando(Math.random() * 1000);
const gen = G.genInt();
const value = gen.generate();
expect(typeof value).toBe('number');
});
test('can generate N arbitrary integers', () => {
const prng = new Prando(Math.random() * 1000);
const gen = G.genInt();
const value = gen.generateN(5);
expect(value.length).toBe(5);
});
test('can generate arbitrary integer within bounds', () => {
const seed = Math.random() * 1000;
const gen = G.genIntWithin(1000, 200000, seed);
for (let i = 0; i < 100; i++) {
const value = gen.generate();
expect(value).toBeGreaterThanOrEqual(1000);
expect(value).toBeLessThanOrEqual(200000);
}
});
test('generated integers are uniformly distributed', () => {
const seed = Math.random() * 1000;
const lb = 0;
const ub = 100000;
const gen = G.genIntWithin(lb, ub, seed);
const range = ub - lb;
const buckets = 100;
const numSamples = 100000;
// indexed by lower bound
const counts: Record<number, number> = {};
for (let i = 1; i <= buckets; i++) {
counts[Math.ceil(range / buckets) * i + lb] = 0;
}
const values = [];
for (let i = 0; i < numSamples; i++) {
const value = gen.generate();
values.push(value);
const bucket = Object.keys(counts).find((b) => value < parseInt(b));
if (!bucket) {
console.log('ignoring bucket for value', value);
} else {
const b = parseInt(bucket);
counts[b] = (counts[b] || 0) + 1;
}
}
// compute χ² statistic
const expectedCount = numSamples / buckets;
const chiSquared = chiSquare(Object.values(counts), new Array(buckets).fill(expectedCount));
// compute p-value and Cramér's V.
const cramerv = computeCramersV(chiSquared, numSamples, buckets);
const pValue = computeChiSquaredPValue(chiSquared, buckets);
console.log(`Chi-squared: ${chiSquared}, p-value: ${pValue}, Cramer V: ${cramerv}`);
expect(cramerv).toBeLessThan(0.015);
expect(pValue).toBeGreaterThan(0.05);
});
test('can generate uniformly distributed arbitrary strings shorter than 256 characters', () => {
const seed = Math.random() * (1 << 31);
const gen = G.genASCIIString(seed);
const buckets = 16;
const numSamples = (1 << 16);
const lengths = new Array(buckets).fill(0);
for (let i = 0; i < numSamples; i++) {
const value = gen.generate();
lengths[value.length % buckets]++;
expect(value.length).toBeLessThan(256);
}
console.log(lengths);
const chiSquared = chiSquare(lengths, new Array(buckets).fill(numSamples / buckets));
const cramerv = computeCramersV(chiSquared, numSamples, buckets);
const pValue = computeChiSquaredPValue(chiSquared, buckets);
console.log(`Chi-squared: ${chiSquared}, p-value: ${pValue}, Cramer V: ${cramerv}`);
expect(cramerv).toBeLessThan(0.015);
});
|