@cloudglides/nox 1.1.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +9 -9
  2. package/example/src/App.css +89 -84
  3. package/example/src/App.jsx +375 -151
  4. package/package.json +7 -6
  5. package/src/core.browser.js +10 -2
  6. package/src/core.js +32 -4
  7. package/src/generators/logistic.js +30 -25
  8. package/src/generators/mixer.js +7 -7
  9. package/src/generators/mt19937.js +10 -7
  10. package/src/generators/pcg64.js +23 -12
  11. package/src/generators/splitmix64.js +12 -6
  12. package/src/generators/tent.js +12 -7
  13. package/src/generators/xorshift64.js +6 -3
  14. package/src/index.d.ts +68 -4
  15. package/src/index.js +154 -2
  16. package/src/rng.browser.js +21 -10
  17. package/src/rng.js +95 -82
  18. package/src/utils/arrays.js +149 -0
  19. package/src/utils/bits.js +146 -21
  20. package/src/utils/categorical.js +68 -31
  21. package/src/utils/combinatorics.js +113 -69
  22. package/src/utils/confidence.js +145 -0
  23. package/src/utils/decomposition.js +204 -0
  24. package/src/utils/distributions-advanced.js +122 -0
  25. package/src/utils/distributions-extra.js +102 -11
  26. package/src/utils/distributions-special.js +77 -20
  27. package/src/utils/distributions.js +99 -35
  28. package/src/utils/effects.js +172 -0
  29. package/src/utils/entropy.browser.js +29 -26
  30. package/src/utils/entropy.js +18 -8
  31. package/src/utils/helpers.js +64 -0
  32. package/src/utils/hypothesis.js +167 -0
  33. package/src/utils/integration.js +137 -0
  34. package/src/utils/interpolation.js +221 -0
  35. package/src/utils/matrix.js +242 -0
  36. package/src/utils/noise.js +36 -22
  37. package/src/utils/odesolvers.js +176 -0
  38. package/src/utils/optimization.js +215 -0
  39. package/src/utils/precomputed.js +166 -0
  40. package/src/utils/probability.js +199 -0
  41. package/src/utils/regression.js +170 -0
  42. package/src/utils/resampling.js +112 -0
  43. package/src/utils/rootfinding.js +158 -0
  44. package/src/utils/sampling.js +86 -77
  45. package/src/utils/seed.js +10 -4
  46. package/src/utils/seeding.js +24 -12
  47. package/src/utils/sequence.js +116 -32
  48. package/src/utils/state.js +48 -36
  49. package/src/utils/statistics.js +64 -2
  50. package/src/utils/stochastic.js +91 -31
  51. package/src/utils/stratified.js +108 -0
  52. package/src/utils/timeseries.js +166 -0
  53. package/src/utils/transforms.js +146 -0
  54. package/test/comprehensive-new.js +126 -0
  55. package/test/comprehensive.js +4 -3
  56. package/test/error-handling.js +49 -0
  57. package/test/new-features.js +52 -0
  58. package/IMPROVEMENTS.md +0 -58
@@ -1,21 +1,59 @@
1
1
  export const beta = (rng, alpha, beta) => {
2
- const u = -Math.log(rng.nextFloat()) / alpha;
3
- const v = -Math.log(rng.nextFloat()) / beta;
4
- return u / (u + v);
5
- };
2
+ if (!rng || typeof rng.nextFloat !== 'function') {
3
+ throw new TypeError('First argument must be RNG instance');
4
+ }
5
+ if (typeof alpha !== 'number') {
6
+ throw new TypeError('alpha must be a number');
7
+ }
8
+ if (alpha <= 0) {
9
+ throw new RangeError('alpha must be positive');
10
+ }
11
+ if (typeof beta !== 'number') {
12
+ throw new TypeError('beta must be a number');
13
+ }
14
+ if (beta <= 0) {
15
+ throw new RangeError('beta must be positive');
16
+ }
17
+ let u = rng.nextFloat();
18
+ while (u === 0 || u === 1) {
19
+ u = rng.nextFloat();
20
+ }
21
+ let v = rng.nextFloat();
22
+ while (v === 0 || v === 1) {
23
+ v = rng.nextFloat();
24
+ }
25
+ const uVal = -Math.log(u) / alpha;
26
+ const vVal = -Math.log(v) / beta;
27
+ return uVal / (uVal + vVal);
28
+ };
6
29
 
7
30
  export const gamma = (rng, shape, scale = 1) => {
8
- if (shape < 1) {
9
- return gamma(rng, shape + 1, scale) * Math.pow(rng.nextFloat(), 1 / shape);
10
- }
31
+ if (!rng || typeof rng.nextFloat !== 'function') {
32
+ throw new TypeError('First argument must be RNG instance');
33
+ }
34
+ if (typeof shape !== 'number') {
35
+ throw new TypeError('shape must be a number');
36
+ }
37
+ if (shape <= 0) {
38
+ throw new RangeError('shape must be positive');
39
+ }
40
+ if (typeof scale !== 'number') {
41
+ throw new TypeError('scale must be a number');
42
+ }
43
+ if (scale <= 0) {
44
+ throw new RangeError('scale must be positive');
45
+ }
46
+ if (shape < 1) {
47
+ return gamma(rng, shape + 1, scale) * Math.pow(rng.nextFloat(), 1 / shape);
48
+ }
11
49
 
12
50
  const d = shape - 1 / 3;
13
51
  const c = 1 / Math.sqrt(9 * d);
14
52
 
15
- let z, v, u;
53
+ let z, v, u, x;
16
54
  do {
17
55
  do {
18
- const x = (rng.nextFloat() * 2 - 1);
56
+ x = (rng.nextFloat() * 2 - 1);
19
57
  v = 1 + c * x;
20
58
  } while (v <= 0);
21
59
 
@@ -28,5 +66,58 @@ export const gamma = (rng, shape, scale = 1) => {
28
66
  };
29
67
 
30
68
  export const chi2 = (rng, k) => {
31
- return gamma(rng, k / 2, 2);
32
- };
69
+ if (!rng || typeof rng.nextFloat !== 'function') {
70
+ throw new TypeError('First argument must be RNG instance');
71
+ }
72
+ if (typeof k !== 'number') {
73
+ throw new TypeError('k must be a number');
74
+ }
75
+ if (k <= 0) {
76
+ throw new RangeError('k must be positive');
77
+ }
78
+ return gamma(rng, k / 2, 2);
79
+ };
80
+
81
+ export const binomial = (rng, n, p) => {
82
+ if (!rng || typeof rng.nextFloat !== 'function') {
83
+ throw new TypeError('First argument must be RNG instance');
84
+ }
85
+ if (typeof n !== 'number' || !Number.isInteger(n)) {
86
+ throw new TypeError('n must be an integer');
87
+ }
88
+ if (n <= 0) {
89
+ throw new RangeError('n must be positive');
90
+ }
91
+ if (typeof p !== 'number') {
92
+ throw new TypeError('p must be a number');
93
+ }
94
+ if (p < 0 || p > 1) {
95
+ throw new RangeError('p must be between 0 and 1');
96
+ }
97
+
98
+ if (p === 0) return 0;
99
+ if (p === 1) return n;
100
+ if (n > 30 && p > 0.1 && p < 0.9) {
101
+ return Math.round(n * p + Math.sqrt(n * p * (1 - p)) * (rng.nextFloat() * 2 - 1));
102
+ }
103
+
104
+ let count = 0;
105
+ for (let i = 0; i < n; i++) {
106
+ if (rng.nextFloat() < p) count++;
107
+ }
108
+ return count;
109
+ };
110
+
111
+ export const geometric = (rng, p) => {
112
+ if (!rng || typeof rng.nextFloat !== 'function') {
113
+ throw new TypeError('First argument must be RNG instance');
114
+ }
115
+ if (typeof p !== 'number') {
116
+ throw new TypeError('p must be a number');
117
+ }
118
+ if (p <= 0 || p > 1) {
119
+ throw new RangeError('p must be between 0 and 1 (exclusive 0)');
120
+ }
121
+
122
+ return Math.floor(Math.log(rng.nextFloat()) / Math.log(1 - p)) + 1;
123
+ };
@@ -1,21 +1,78 @@
1
1
  export const weibull = (rng, shape, scale = 1) => {
2
- const u = rng.nextFloat();
3
- return scale * Math.pow(-Math.log(1 - u), 1 / shape);
4
- };
5
-
6
- export const lognormal = (rng, mu, sigma) => {
7
- const u1 = rng.nextFloat();
8
- const u2 = rng.nextFloat();
9
- const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
10
- return Math.exp(mu + sigma * z);
11
- };
12
-
13
- export const rayleigh = (rng, sigma = 1) => {
14
- const u = rng.nextFloat();
15
- return sigma * Math.sqrt(-2 * Math.log(u));
16
- };
17
-
18
- export const cauchy = (rng, median = 0, scale = 1) => {
19
- const u = rng.nextFloat();
20
- return median + scale * Math.tan(Math.PI * (u - 0.5));
21
- };
2
+ if (!rng || typeof rng.nextFloat !== 'function') {
3
+ throw new TypeError('First argument must be RNG instance');
4
+ }
5
+ if (typeof shape !== 'number') {
6
+ throw new TypeError('shape must be a number');
7
+ }
8
+ if (shape <= 0) {
9
+ throw new RangeError('shape must be positive');
10
+ }
11
+ if (typeof scale !== 'number') {
12
+ throw new TypeError('scale must be a number');
13
+ }
14
+ if (scale <= 0) {
15
+ throw new RangeError('scale must be positive');
16
+ }
17
+ let u = rng.nextFloat();
18
+ while (u === 0 || u === 1) {
19
+ u = rng.nextFloat();
20
+ }
21
+ return scale * Math.pow(-Math.log(1 - u), 1 / shape);
22
+ };
23
+
24
+ export const lognormal = (rng, mu, sigma) => {
25
+ if (!rng || typeof rng.nextFloat !== 'function') {
26
+ throw new TypeError('First argument must be RNG instance');
27
+ }
28
+ if (typeof mu !== 'number') {
29
+ throw new TypeError('mu must be a number');
30
+ }
31
+ if (typeof sigma !== 'number') {
32
+ throw new TypeError('sigma must be a number');
33
+ }
34
+ if (sigma <= 0) {
35
+ throw new RangeError('sigma must be positive');
36
+ }
37
+ let u1 = rng.nextFloat();
38
+ while (u1 === 0 || u1 === 1) {
39
+ u1 = rng.nextFloat();
40
+ }
41
+ const u2 = rng.nextFloat();
42
+ const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
43
+ return Math.exp(mu + sigma * z);
44
+ };
45
+
46
+ export const rayleigh = (rng, sigma = 1) => {
47
+ if (!rng || typeof rng.nextFloat !== 'function') {
48
+ throw new TypeError('First argument must be RNG instance');
49
+ }
50
+ if (typeof sigma !== 'number') {
51
+ throw new TypeError('sigma must be a number');
52
+ }
53
+ if (sigma <= 0) {
54
+ throw new RangeError('sigma must be positive');
55
+ }
56
+ let u = rng.nextFloat();
57
+ while (u === 0 || u === 1) {
58
+ u = rng.nextFloat();
59
+ }
60
+ return sigma * Math.sqrt(-2 * Math.log(u));
61
+ };
62
+
63
+ export const cauchy = (rng, median = 0, scale = 1) => {
64
+ if (!rng || typeof rng.nextFloat !== 'function') {
65
+ throw new TypeError('First argument must be RNG instance');
66
+ }
67
+ if (typeof median !== 'number') {
68
+ throw new TypeError('median must be a number');
69
+ }
70
+ if (typeof scale !== 'number') {
71
+ throw new TypeError('scale must be a number');
72
+ }
73
+ if (scale <= 0) {
74
+ throw new RangeError('scale must be positive');
75
+ }
76
+ const u = rng.nextFloat();
77
+ return median + scale * Math.tan(Math.PI * (u - 0.5));
78
+ };
@@ -1,15 +1,18 @@
1
1
  const TWO_PI = 2 * Math.PI;
2
2
 
3
3
  export const normal = (rng, mean = 0, stddev = 1) => {
4
- if (!rng || typeof rng.nextFloat !== 'function') {
5
- throw new TypeError('First argument must be RNG instance');
6
- }
7
- if (typeof mean !== 'number' || typeof stddev !== 'number') {
8
- throw new TypeError('mean and stddev must be numbers');
9
- }
10
- if (stddev <= 0) {
11
- throw new Error('stddev must be positive');
12
- }
4
+ if (!rng || typeof rng.nextFloat !== 'function') {
5
+ throw new TypeError('First argument must be RNG instance');
6
+ }
7
+ if (typeof mean !== 'number') {
8
+ throw new TypeError('mean must be a number');
9
+ }
10
+ if (typeof stddev !== 'number') {
11
+ throw new TypeError('stddev must be a number');
12
+ }
13
+ if (stddev <= 0) {
14
+ throw new RangeError('stddev must be positive');
15
+ }
13
16
 
14
17
  let u1, u2;
15
18
  do {
@@ -22,27 +25,34 @@ export const normal = (rng, mean = 0, stddev = 1) => {
22
25
  };
23
26
 
24
27
  export const exponential = (rng, lambda = 1) => {
25
- if (!rng || typeof rng.nextFloat !== 'function') {
26
- throw new TypeError('First argument must be RNG instance');
27
- }
28
- if (typeof lambda !== 'number' || lambda <= 0) {
29
- throw new Error('lambda must be a positive number');
30
- }
31
-
32
- let u = rng.nextFloat();
33
- while (u === 0 || u === 1) {
34
- u = rng.nextFloat();
35
- }
36
- return -Math.log(1 - u) / lambda;
37
- };
28
+ if (!rng || typeof rng.nextFloat !== 'function') {
29
+ throw new TypeError('First argument must be RNG instance');
30
+ }
31
+ if (typeof lambda !== 'number') {
32
+ throw new TypeError('lambda must be a number');
33
+ }
34
+ if (lambda <= 0) {
35
+ throw new RangeError('lambda must be positive');
36
+ }
37
+
38
+ let u = rng.nextFloat();
39
+ while (u === 0 || u === 1) {
40
+ u = rng.nextFloat();
41
+ }
42
+ const result = -Math.log(1 - u) / lambda;
43
+ return Number.isFinite(result) ? result : -Math.log(u) / lambda;
44
+ };
38
45
 
39
46
  export const poisson = (rng, lambda) => {
40
- if (!rng || typeof rng.nextFloat !== 'function') {
41
- throw new TypeError('First argument must be RNG instance');
42
- }
43
- if (typeof lambda !== 'number' || lambda <= 0) {
44
- throw new Error('lambda must be a positive number');
45
- }
47
+ if (!rng || typeof rng.nextFloat !== 'function') {
48
+ throw new TypeError('First argument must be RNG instance');
49
+ }
50
+ if (typeof lambda !== 'number') {
51
+ throw new TypeError('lambda must be a number');
52
+ }
53
+ if (lambda <= 0) {
54
+ throw new RangeError('lambda must be positive');
55
+ }
46
56
 
47
57
  const L = Math.exp(-lambda);
48
58
  let k = 0;
@@ -57,15 +67,69 @@ export const poisson = (rng, lambda) => {
57
67
  };
58
68
 
59
69
  export const uniform = (rng, min, max) => {
60
- if (!rng || typeof rng.nextFloat !== 'function') {
61
- throw new TypeError('First argument must be RNG instance');
70
+ if (!rng || typeof rng.nextFloat !== 'function') {
71
+ throw new TypeError('First argument must be RNG instance');
72
+ }
73
+ if (typeof min !== 'number') {
74
+ throw new TypeError('min must be a number');
75
+ }
76
+ if (typeof max !== 'number') {
77
+ throw new TypeError('max must be a number');
78
+ }
79
+ if (min >= max) {
80
+ throw new RangeError('min must be less than max');
81
+ }
82
+
83
+ return min + rng.nextFloat() * (max - min);
84
+ };
85
+
86
+ export const normals = (rng, count, mean = 0, stddev = 1) => {
87
+ if (typeof count !== 'number' || !Number.isInteger(count)) {
88
+ throw new TypeError('count must be an integer');
62
89
  }
63
- if (typeof min !== 'number' || typeof max !== 'number') {
64
- throw new TypeError('min and max must be numbers');
90
+ if (count <= 0) {
91
+ throw new RangeError('count must be positive');
65
92
  }
66
- if (min >= max) {
67
- throw new Error('min must be less than max');
93
+ const result = new Array(count);
94
+ let cached = null;
95
+
96
+ for (let i = 0; i < count; i++) {
97
+ if (cached !== null) {
98
+ result[i] = cached * stddev + mean;
99
+ cached = null;
100
+ } else {
101
+ let u1, u2;
102
+ do {
103
+ u1 = rng.nextFloat();
104
+ u2 = rng.nextFloat();
105
+ } while (u1 <= 0 || u2 <= 0);
106
+
107
+ const z0 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(TWO_PI * u2);
108
+ const z1 = Math.sqrt(-2 * Math.log(u1)) * Math.sin(TWO_PI * u2);
109
+ result[i] = z0 * stddev + mean;
110
+ cached = z1;
111
+ }
112
+ }
113
+
114
+ return result;
115
+ };
116
+
117
+ export const exponentials = (rng, count, lambda = 1) => {
118
+ if (typeof count !== 'number' || !Number.isInteger(count)) {
119
+ throw new TypeError('count must be an integer');
120
+ }
121
+ if (count <= 0) {
122
+ throw new RangeError('count must be positive');
123
+ }
124
+ const result = new Array(count);
125
+
126
+ for (let i = 0; i < count; i++) {
127
+ let u = rng.nextFloat();
128
+ while (u === 0 || u === 1) {
129
+ u = rng.nextFloat();
130
+ }
131
+ result[i] = -Math.log(1 - u) / lambda;
68
132
  }
69
133
 
70
- return min + rng.nextFloat() * (max - min);
134
+ return result;
71
135
  };
@@ -0,0 +1,172 @@
1
+ export function cohensD(group1, group2) {
2
+ if (!Array.isArray(group1) || group1.length === 0) {
3
+ throw new TypeError('group1 must be non-empty array');
4
+ }
5
+ if (!Array.isArray(group2) || group2.length === 0) {
6
+ throw new TypeError('group2 must be non-empty array');
7
+ }
8
+
9
+ const n1 = group1.length;
10
+ const n2 = group2.length;
11
+
12
+ const mean1 = group1.reduce((a, b) => a + b, 0) / n1;
13
+ const mean2 = group2.reduce((a, b) => a + b, 0) / n2;
14
+
15
+ let var1 = 0, var2 = 0;
16
+ for (let i = 0; i < n1; i++) {
17
+ var1 += (group1[i] - mean1) ** 2;
18
+ }
19
+ for (let i = 0; i < n2; i++) {
20
+ var2 += (group2[i] - mean2) ** 2;
21
+ }
22
+
23
+ var1 /= n1 - 1;
24
+ var2 /= n2 - 1;
25
+
26
+ const pooledStd = Math.sqrt(((n1 - 1) * var1 + (n2 - 1) * var2) / (n1 + n2 - 2));
27
+
28
+ return (mean1 - mean2) / pooledStd;
29
+ }
30
+
31
+ export function hedgesG(group1, group2) {
32
+ const d = cohensD(group1, group2);
33
+ const n1 = group1.length;
34
+ const n2 = group2.length;
35
+ const n = n1 + n2;
36
+ const correction = 1 - (3 / (4 * n - 9));
37
+
38
+ return d * correction;
39
+ }
40
+
41
+ export function correlation(x, y) {
42
+ if (!Array.isArray(x) || !Array.isArray(y)) {
43
+ throw new TypeError('x and y must be arrays');
44
+ }
45
+ if (x.length !== y.length || x.length === 0) {
46
+ throw new RangeError('x and y must have same non-zero length');
47
+ }
48
+
49
+ const n = x.length;
50
+ const meanX = x.reduce((a, b) => a + b, 0) / n;
51
+ const meanY = y.reduce((a, b) => a + b, 0) / n;
52
+
53
+ let sumXY = 0, sumX2 = 0, sumY2 = 0;
54
+
55
+ for (let i = 0; i < n; i++) {
56
+ const dx = x[i] - meanX;
57
+ const dy = y[i] - meanY;
58
+ sumXY += dx * dy;
59
+ sumX2 += dx * dx;
60
+ sumY2 += dy * dy;
61
+ }
62
+
63
+ return sumXY / Math.sqrt(sumX2 * sumY2);
64
+ }
65
+
66
+ export function cramersV(contingencyTable) {
67
+ if (!Array.isArray(contingencyTable) || contingencyTable.length === 0) {
68
+ throw new TypeError('contingencyTable must be non-empty array');
69
+ }
70
+
71
+ const rows = contingencyTable.length;
72
+ const cols = contingencyTable[0].length;
73
+
74
+ let n = 0;
75
+ for (let i = 0; i < rows; i++) {
76
+ for (let j = 0; j < cols; j++) {
77
+ if (typeof contingencyTable[i][j] !== 'number') {
78
+ throw new TypeError('all table entries must be numbers');
79
+ }
80
+ n += contingencyTable[i][j];
81
+ }
82
+ }
83
+
84
+ let chi2 = 0;
85
+ const rowTotals = new Array(rows);
86
+ const colTotals = new Array(cols);
87
+
88
+ for (let i = 0; i < rows; i++) {
89
+ rowTotals[i] = 0;
90
+ for (let j = 0; j < cols; j++) {
91
+ rowTotals[i] += contingencyTable[i][j];
92
+ }
93
+ }
94
+
95
+ for (let j = 0; j < cols; j++) {
96
+ colTotals[j] = 0;
97
+ for (let i = 0; i < rows; i++) {
98
+ colTotals[j] += contingencyTable[i][j];
99
+ }
100
+ }
101
+
102
+ for (let i = 0; i < rows; i++) {
103
+ for (let j = 0; j < cols; j++) {
104
+ const expected = (rowTotals[i] * colTotals[j]) / n;
105
+ const diff = contingencyTable[i][j] - expected;
106
+ chi2 += (diff * diff) / expected;
107
+ }
108
+ }
109
+
110
+ const minDim = Math.min(rows, cols) - 1;
111
+ return Math.sqrt(chi2 / (n * minDim));
112
+ }
113
+
114
+ export function etaSquared(groups) {
115
+ if (!Array.isArray(groups) || groups.length === 0) {
116
+ throw new TypeError('groups must be non-empty array of arrays');
117
+ }
118
+
119
+ let totalN = 0;
120
+ const groupMeans = new Array(groups.length);
121
+ const groupSizes = new Array(groups.length);
122
+
123
+ for (let i = 0; i < groups.length; i++) {
124
+ if (!Array.isArray(groups[i])) {
125
+ throw new TypeError('each group must be an array');
126
+ }
127
+ groupSizes[i] = groups[i].length;
128
+ totalN += groupSizes[i];
129
+ groupMeans[i] = groups[i].reduce((a, b) => a + b, 0) / groupSizes[i];
130
+ }
131
+
132
+ const grandMean = groups.flat().reduce((a, b) => a + b, 0) / totalN;
133
+
134
+ let ssB = 0;
135
+ for (let i = 0; i < groups.length; i++) {
136
+ ssB += groupSizes[i] * (groupMeans[i] - grandMean) ** 2;
137
+ }
138
+
139
+ let ssT = 0;
140
+ for (let i = 0; i < groups.length; i++) {
141
+ for (let j = 0; j < groups[i].length; j++) {
142
+ ssT += (groups[i][j] - grandMean) ** 2;
143
+ }
144
+ }
145
+
146
+ return ssB / ssT;
147
+ }
148
+
149
+ export function glasssDelta(control, treatment, pooledStd = null) {
150
+ if (!Array.isArray(control) || control.length === 0) {
151
+ throw new TypeError('control must be non-empty array');
152
+ }
153
+ if (!Array.isArray(treatment) || treatment.length === 0) {
154
+ throw new TypeError('treatment must be non-empty array');
155
+ }
156
+
157
+ const meanControl = control.reduce((a, b) => a + b, 0) / control.length;
158
+ const meanTreatment = treatment.reduce((a, b) => a + b, 0) / treatment.length;
159
+
160
+ let std = pooledStd;
161
+
162
+ if (std === null) {
163
+ let varControl = 0;
164
+ for (let i = 0; i < control.length; i++) {
165
+ varControl += (control[i] - meanControl) ** 2;
166
+ }
167
+ varControl /= control.length - 1;
168
+ std = Math.sqrt(varControl);
169
+ }
170
+
171
+ return (meanTreatment - meanControl) / std;
172
+ }
@@ -1,5 +1,6 @@
1
1
  let cryptoCache = null;
2
2
  let cryptoCacheTime = 0;
3
+ let cryptoCacheBytes = 0;
3
4
 
4
5
  export const fromPerformance = () => {
5
6
  try {
@@ -18,12 +19,12 @@ export const fromMemory = () => {
18
19
  };
19
20
 
20
21
  export const fromCrypto = (bytes = 8) => {
21
- try {
22
- const now = Date.now();
23
-
24
- if (cryptoCache && (now - cryptoCacheTime) < 100) {
25
- return cryptoCache;
26
- }
22
+ try {
23
+ const now = Date.now();
24
+
25
+ if (cryptoCache && (now - cryptoCacheTime) < 100 && cryptoCacheBytes === bytes) {
26
+ return cryptoCache;
27
+ }
27
28
 
28
29
  let val = 0n;
29
30
 
@@ -39,25 +40,27 @@ export const fromCrypto = (bytes = 8) => {
39
40
 
40
41
  cryptoCache = val;
41
42
  cryptoCacheTime = now;
43
+ cryptoCacheBytes = bytes;
42
44
  return val;
43
- } catch {
45
+ } catch {
44
46
  return BigInt(Math.random() * Number.MAX_SAFE_INTEGER);
45
- }
46
- };
47
-
48
- export const combined = () => {
49
- const perf = fromPerformance();
50
- const mem = fromMemory();
51
- const crypto = fromCrypto();
52
-
53
- let mix = perf ^ mem ^ crypto;
54
- mix = mix ^ (mix >> 33n);
55
- mix = (mix * 0xff51afd7ed558ccdn) & ((1n << 64n) - 1n);
56
-
57
- return mix !== 0n ? mix : 1n;
58
- };
59
-
60
- export const clearCryptoCache = () => {
61
- cryptoCache = null;
62
- cryptoCacheTime = 0;
63
- };
47
+ }
48
+ };
49
+
50
+ export const combined = () => {
51
+ const perf = fromPerformance();
52
+ const mem = fromMemory();
53
+ const crypto = fromCrypto();
54
+
55
+ let mix = perf ^ mem ^ crypto;
56
+ mix = mix ^ (mix >> 33n);
57
+ mix = (mix * 0xff51afd7ed558ccdn) & ((1n << 64n) - 1n);
58
+
59
+ return mix !== 0n ? mix : 1n;
60
+ };
61
+
62
+ export const clearCryptoCache = () => {
63
+ cryptoCache = null;
64
+ cryptoCacheTime = 0;
65
+ cryptoCacheBytes = 0;
66
+ };
@@ -1,5 +1,6 @@
1
1
  let cryptoCache = null;
2
2
  let cryptoCacheTime = 0;
3
+ let cryptoCacheBytes = 0;
3
4
 
4
5
  const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
5
6
  const isBrowser = typeof window !== 'undefined';
@@ -46,13 +47,20 @@ export const fromMemory = () => {
46
47
  };
47
48
 
48
49
  export const fromCrypto = (bytes = 8) => {
49
- try {
50
- const now = Date.now();
51
-
52
- if (cryptoCache && (now - cryptoCacheTime) < 100) {
53
- return cryptoCache;
50
+ if (typeof bytes !== 'number' || !Number.isInteger(bytes)) {
51
+ throw new TypeError('bytes must be an integer');
52
+ }
53
+ if (bytes <= 0) {
54
+ throw new RangeError('bytes must be positive');
54
55
  }
55
56
 
57
+ try {
58
+ const now = Date.now();
59
+
60
+ if (cryptoCache && (now - cryptoCacheTime) < 100 && cryptoCacheBytes === bytes) {
61
+ return cryptoCache;
62
+ }
63
+
56
64
  let val = 0n;
57
65
 
58
66
  if (randomBytesImpl) {
@@ -72,6 +80,7 @@ export const fromCrypto = (bytes = 8) => {
72
80
 
73
81
  cryptoCache = val;
74
82
  cryptoCacheTime = now;
83
+ cryptoCacheBytes = bytes;
75
84
  return val;
76
85
  } catch {
77
86
  return BigInt(Math.random() * Number.MAX_SAFE_INTEGER);
@@ -91,6 +100,7 @@ export const combined = () => {
91
100
  };
92
101
 
93
102
  export const clearCryptoCache = () => {
94
- cryptoCache = null;
95
- cryptoCacheTime = 0;
96
- };
103
+ cryptoCache = null;
104
+ cryptoCacheTime = 0;
105
+ cryptoCacheBytes = 0;
106
+ };