nano-benchmark 1.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.
@@ -0,0 +1,35 @@
1
+ import {measure, measurePar} from './runner.js';
2
+ import kwtest from '../significance/kwtest.js';
3
+ import mwtest from '../significance/mwtest.js';
4
+
5
+ const ALPHA = 0.05;
6
+
7
+ const compare = async (inputs, options = {}, report) => {
8
+ const keys = Object.keys(inputs);
9
+ if (keys.length < 2) throw new Error('The "inputs" is supposed to have 2 or more samples.');
10
+
11
+ options = Object.assign({alpha: ALPHA}, options);
12
+ const measureFn = options.usePar ? measurePar : measure;
13
+
14
+ const stats = new Array(keys.length);
15
+ for (let i = 0; i < keys.length; ++i) {
16
+ stats[i] = await measureFn(inputs[keys[i]], options, report);
17
+ stats[i].ensureSorted();
18
+ }
19
+ const reps = stats.map(stat => stat.reps);
20
+ stats.forEach(stat => stat.normalizeReps());
21
+
22
+ report?.('calculating-significance', {stats, options});
23
+ let results;
24
+ if (keys.length > 2) {
25
+ results = kwtest(stats.map(stat => stat.data), options.alpha);
26
+ } else {
27
+ results = mwtest(stats[0].data, stats[1].data, options.alpha);
28
+ }
29
+ results.data = stats.map(stat => stat.data);
30
+ results.reps = reps;
31
+ report?.('significance-results', results);
32
+ return results;
33
+ };
34
+
35
+ export default compare;
@@ -0,0 +1,173 @@
1
+ import {performance} from 'node:perf_hooks';
2
+
3
+ export const nextLevel = n => {
4
+ if (n < 1) return 1;
5
+ let exp = 0;
6
+ while (!(n % 10)) {
7
+ ++exp;
8
+ n = n / 10;
9
+ }
10
+ if (n < 5) {
11
+ n = n < 2 ? 2 : 5;
12
+ } else if (n < 10) {
13
+ n = 10;
14
+ } else {
15
+ n = n << 1;
16
+ }
17
+ while (exp--) {
18
+ n *= 10;
19
+ }
20
+ return n;
21
+ };
22
+
23
+ export const findLevel = (fn, {threshold = 20, startFrom = 1, timeout = 5} = {}, report) =>
24
+ new Promise((resolve, reject) => {
25
+ const bench = async n => {
26
+ report && (await report('finding-level', {n}));
27
+ try {
28
+ const start = performance.now(),
29
+ result = fn(n),
30
+ finish = performance.now();
31
+ if (result && typeof result.then == 'function') {
32
+ // thenable
33
+ result.then(async () => {
34
+ const finish = performance.now();
35
+ if (finish - start >= threshold) return resolve(n);
36
+ report && (await report('finding-level-next', {n, time: finish - start}));
37
+ setTimeout(bench, timeout, nextLevel(n));
38
+ }, reject);
39
+ return;
40
+ }
41
+ if (finish - start >= threshold) return resolve(n);
42
+ report && (await report('finding-level-next', {n, time: finish - start}));
43
+ setTimeout(bench, timeout, nextLevel(n));
44
+ } catch (error) {
45
+ reject(error);
46
+ }
47
+ };
48
+ bench(startFrom);
49
+ });
50
+
51
+ export const benchmark = (fn, n) =>
52
+ new Promise((resolve, reject) => {
53
+ try {
54
+ const start = performance.now(),
55
+ result = fn(n),
56
+ finish = performance.now();
57
+ if (result && typeof result.then == 'function') {
58
+ // thenable
59
+ result.then(() => {
60
+ const finish = performance.now();
61
+ resolve(finish - start);
62
+ }, reject);
63
+ return;
64
+ }
65
+ resolve(finish - start);
66
+ } catch (error) {
67
+ reject(error);
68
+ }
69
+ });
70
+
71
+ export const benchmarkSeries = async (
72
+ fn,
73
+ n,
74
+ {nSeries = 100, timeout = 5, DataArray = Array} = {}
75
+ ) => {
76
+ const data = new DataArray(nSeries);
77
+
78
+ const bench = async (nSeries, resolve, reject) => {
79
+ --nSeries;
80
+ try {
81
+ data[nSeries] = await benchmark(fn, n);
82
+ if (nSeries) {
83
+ setTimeout(bench, timeout, nSeries, resolve, reject);
84
+ } else {
85
+ resolve();
86
+ }
87
+ } catch (error) {
88
+ reject(error);
89
+ }
90
+ };
91
+
92
+ await new Promise((resolve, reject) => bench(nSeries, resolve, reject));
93
+
94
+ return data;
95
+ };
96
+
97
+ export const benchmarkSeriesPar = async (fn, n, {nSeries = 100, DataArray = Array} = {}) => {
98
+ const benchmarks = [];
99
+ for (; nSeries > 0; --nSeries) benchmarks.push(benchmark(fn, n));
100
+ const results = await Promise.all(benchmarks);
101
+ return DataArray === Array ? results : DataArray.from(results);
102
+ };
103
+
104
+ export class Stats {
105
+ constructor(object) {
106
+ Object.assign(this, object);
107
+ }
108
+
109
+ static sortNumbersAsc = (a, b) => a - b;
110
+
111
+ ensureSorted() {
112
+ if (!this.sorted) {
113
+ this.data.sort(Stats.sortNumbersAsc);
114
+ this.sorted = true;
115
+ }
116
+ return this;
117
+ }
118
+
119
+ normalizeReps() {
120
+ if (this.reps !== 1) {
121
+ const data = this.data,
122
+ reps = this.reps,
123
+ size = data.length;
124
+ for (let i = 0; i < size; ++i) {
125
+ data[i] /= reps;
126
+ }
127
+ this.reps = 1;
128
+ }
129
+ return this;
130
+ }
131
+
132
+ copyStats() {
133
+ return new Stats({...this, data: this.data.slice()});
134
+ }
135
+ }
136
+
137
+ export const measure = async (
138
+ fn,
139
+ {nSeries = 100, threshold = 20, startFrom = 1, timeout = 5, DataArray = Array} = {},
140
+ report
141
+ ) => {
142
+ report?.('finding-reps');
143
+ const reps = startFrom < 0 ? -startFrom : await findLevel(fn, {threshold, startFrom, timeout});
144
+ report?.('found-reps', {reps});
145
+ report?.('starting-benchmarks', {nSeries, reps});
146
+ const start = performance.now(),
147
+ data = await benchmarkSeries(fn, reps, {nSeries, timeout, DataArray}),
148
+ finish = performance.now(),
149
+ result = {data, reps, time: finish - start};
150
+ report?.('finished-benchmarks', {...result, nSeries});
151
+ return new Stats(result);
152
+ };
153
+
154
+ export const measurePar = async (
155
+ fn,
156
+ {nSeries = 100, threshold = 20, startFrom = 1, timeout = 5, DataArray = Array} = {},
157
+ report
158
+ ) => {
159
+ report?.('finding-reps');
160
+ const reps = startFrom < 0 ? -startFrom : await findLevel(fn, {threshold, startFrom, timeout});
161
+ report?.('found-reps', {reps});
162
+ report?.('starting-benchmarks', {nSeries, reps});
163
+ const start = performance.now(),
164
+ data = await benchmarkSeriesPar(fn, reps, {nSeries, DataArray}),
165
+ finish = performance.now(),
166
+ result = {data, reps, time: finish - start};
167
+ report?.('finished-benchmarks', {...result, nSeries});
168
+ return new Stats(result);
169
+ };
170
+
171
+ export const wrapper = fn => n => {
172
+ for (let i = 0; i < n; ++i) fn();
173
+ };
package/src/median.js ADDED
@@ -0,0 +1,39 @@
1
+ export const median = data => {
2
+ let step = 1,
3
+ n = Math.floor(data.length / 3);
4
+ for (;;) {
5
+ for (let i = 0; i < n; ++i) {
6
+ const ai = 3 * step * i,
7
+ bi = ai + step,
8
+ ci = bi + step;
9
+ let a = data[ai],
10
+ b = data[bi],
11
+ c = data[ci];
12
+ if (b < a) [a, b] = [b, a];
13
+ if (c < b) [b, c] = [c, b];
14
+ if (b < a) [a, b] = [b, a];
15
+ data[ai] = b;
16
+ data[bi] = a;
17
+ data[ci] = c;
18
+ }
19
+
20
+ const last = Math.ceil((data.length - 3 * step * n) / step);
21
+ if (last == 2) {
22
+ const ai = 3 * step * n,
23
+ bi = ai + step;
24
+ let a = data[ai],
25
+ b = data[bi];
26
+ if (b < a) [a, b] = [b, a];
27
+ data[ai] = b;
28
+ data[bi] = a;
29
+ }
30
+
31
+ step *= 3;
32
+ if (step >= data.length) break;
33
+ n = Math.floor(data.length / (3 * step));
34
+ }
35
+
36
+ return data[0];
37
+ };
38
+
39
+ export default median;
@@ -0,0 +1,43 @@
1
+ // Two sample Kolmogorov-Smirnov significance test
2
+ // based on https://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test
3
+
4
+ // sup(F1(x) - F2(x))
5
+ const sup = (sorted1, sorted2) => {
6
+ let size1 = sorted1.length,
7
+ size2 = sorted2.length,
8
+ i = 0,
9
+ j = 0,
10
+ result = 0;
11
+ while (i < size1 && j < size2) {
12
+ const a = sorted1[i],
13
+ b = sorted2[j];
14
+ if (a < b) {
15
+ for (++i; i < size1 && a === sorted1[i]; ++i);
16
+ } else if (b < a) {
17
+ for (++j; j < size2 && b === sorted2[j]; ++j);
18
+ } else {
19
+ for (++i; i < size1 && a === sorted1[i]; ++i);
20
+ for (++j; j < size2 && b === sorted2[j]; ++j);
21
+ }
22
+ result = Math.max(result, Math.abs(i / size1 - j / size2));
23
+ }
24
+ while (i < size1) {
25
+ const a = sorted1[i];
26
+ for (++i; i < size1 && a === sorted1[i]; ++i);
27
+ result = Math.max(result, Math.abs(i / size1 - 1));
28
+ }
29
+ while (j < size2) {
30
+ const b = sorted2[j];
31
+ for (++j; j < size2 && b === sorted2[j]; ++j);
32
+ result = Math.max(result, Math.abs(1 - j / size2));
33
+ }
34
+ return result;
35
+ };
36
+
37
+ export const kstest = (sorted1, sorted2, alpha = 0.05) => {
38
+ const d = sup(sorted1, sorted2),
39
+ limit = Math.sqrt((-Math.log(alpha / 2) / 2) * (1 / sorted1.length + 1 / sorted2.length));
40
+ return {value: d, alpha, limit, different: d > limit};
41
+ };
42
+
43
+ export default kstest;
@@ -0,0 +1,81 @@
1
+ // Kruskal-Wallis significance test
2
+ // based on https://en.wikipedia.org/wiki/Kruskal%E2%80%93Wallis_one-way_analysis_of_variance
3
+ // beta approximation: https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.661.7863&rep=rep1&type=pdf
4
+
5
+ import betaPpf from '../stats/beta-ppf.js';
6
+ import chiSquaredPpf from '../stats/chi-squared-ppf.js';
7
+ import rank, {getTotal} from '../stats/rank.js';
8
+
9
+ export const getParameters = (groups, N = getTotal(groups)) => {
10
+ const k = groups.length,
11
+ mu = k - 1,
12
+ nu = (N * N * N - groups.reduce((acc, {length: n}) => acc + n * n * n, 0)) / N / (N + 1),
13
+ sigmaSquared =
14
+ 2 * mu -
15
+ (0.4 * (3 * k * k - 6 * k + N * (2 * k * k - 6 * k + 1))) / N / (N + 1) -
16
+ 1.2 * groups.reduce((acc, group) => acc + 1 / group.length, 0),
17
+ a = (mu * ((mu * (nu - mu)) / sigmaSquared - 1)) / nu,
18
+ b = a * (nu / mu - 1);
19
+ return {mu, nu, sigmaSquared, a, b, k, N};
20
+ };
21
+
22
+ export const rankData = groups => {
23
+ const {N, k, ranked: t, groupRank, avgGroupRank, avgRank} = rank(groups),
24
+ avgRankC = N * avgRank * avgRank;
25
+
26
+ // calculate required sums
27
+ let numerator = 0,
28
+ T = 0;
29
+ for (let i = 0; i < avgGroupRank.length; ++i) {
30
+ const x = avgGroupRank[i] - avgRank;
31
+ numerator += groups[i].length * x * x;
32
+ T += (groupRank[i] * groupRank[i]) / groups[i].length - avgRankC;
33
+ }
34
+
35
+ let denominator = 0,
36
+ S2 = 0;
37
+ for (let i = 0; i < t.length; ++i) {
38
+ const x = t[i].rank - avgRank;
39
+ denominator += x * x;
40
+ S2 = t[i].rank * t[i].rank - avgRankC;
41
+ }
42
+
43
+ S2 /= N - 1;
44
+ T /= S2;
45
+
46
+ // calculate and return H statistics
47
+ return {H: ((N - 1) * numerator) / denominator, T, S2, groupRank, avgGroupRank, avgRank, k, N};
48
+ };
49
+
50
+ export const kwtest = (sortedArrays, alpha = 0.05) => {
51
+ if (sortedArrays.length < 2) throw new Error('Two or more data arrays were expected');
52
+
53
+ const {a, b, nu, k, N} = getParameters(sortedArrays),
54
+ {H, T, S2, avgGroupRank} = rankData(sortedArrays),
55
+ limit = betaPpf(1 - alpha, a, b) * nu, // Hc
56
+ results = {value: H, alpha, limit, different: H > limit};
57
+
58
+ if (!results.different || k < 3) return results;
59
+
60
+ // post-hoc tests
61
+
62
+ const m = new Array(k),
63
+ C = chiSquaredPpf(1 - alpha / 2, N - k) * Math.sqrt((S2 * (N - 1 - T)) / (N - k));
64
+
65
+ for (let i = 0; i < k; ++i) {
66
+ m[i] = new Array(k);
67
+ }
68
+ for (let i = 0; i < k; ++i) {
69
+ m[i][i] = false;
70
+ for (let j = i + 1; j < k; ++j) {
71
+ m[i][j] = m[j][i] =
72
+ Math.abs(avgGroupRank[i] - avgGroupRank[j]) >
73
+ C * Math.sqrt(1 / sortedArrays[i].length + 1 / sortedArrays[j].length);
74
+ }
75
+ }
76
+
77
+ results.groupDifference = m;
78
+ return results;
79
+ };
80
+
81
+ export default kwtest;
@@ -0,0 +1,35 @@
1
+ // Mann-Whitney U test
2
+ // based on https://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U_test
3
+
4
+ import rank from '../stats/rank.js';
5
+ import zPpf from '../stats/z-ppf.js';
6
+
7
+ export const mwtest = (sorted1, sorted2, alpha = 0.05) => {
8
+ const {groupRank, ranked: t} = rank([sorted1, sorted2]),
9
+ u1 = groupRank[0] - (sorted1.length * (sorted1.length + 1)) / 2,
10
+ u2 = groupRank[1] - (sorted2.length * (sorted2.length + 1)) / 2,
11
+ u = Math.min(u1, u2);
12
+
13
+ const m = (sorted1.length * sorted2.length) / 2;
14
+
15
+ let tiesC = 0;
16
+ for (let i = 0, size = t.length; i < size; ) {
17
+ const rank = t[i].rank;
18
+ let j = i + 1;
19
+ while (j < size && rank === t[j].rank) ++j;
20
+ const nt = j - i;
21
+ i = j;
22
+ if (nt > 1) {
23
+ tiesC += nt * nt * nt - nt;
24
+ }
25
+ }
26
+ const n = sorted1.length + sorted2.length,
27
+ s = Math.sqrt(((sorted1.length * sorted2.length) / 12) * (n + 1 - tiesC / n / (n - 1)));
28
+
29
+ const z = (u - m) / s,
30
+ zc = zPpf(alpha / 2);
31
+
32
+ return {value: z, alpha, limit: zc, different: z < zc || z > -zc};
33
+ };
34
+
35
+ export default mwtest;
@@ -0,0 +1,13 @@
1
+ import ppf from './ppf.js';
2
+
3
+ // percent point function
4
+ const betaPpf = (z, a, b) => {
5
+ const fn = z => {
6
+ if (z == 0 || z == 1) return 0;
7
+ const result = Math.pow(z, a - 1) * Math.pow(1 - z, b - 1);
8
+ return isFinite(result) ? result : 0;
9
+ };
10
+ return ppf(fn, z);
11
+ };
12
+
13
+ export default betaPpf;
@@ -0,0 +1,26 @@
1
+ const LIMIT = 1000;
2
+ const EPSILON = 1e-30;
3
+
4
+ export const incompleteBeta = (z, a, b) => {
5
+ let x = 1,
6
+ result = 1 / a;
7
+ for (let i = 1; i < LIMIT; ++i) {
8
+ x *= ((i - b) / i) * z;
9
+ const previous = result;
10
+ result += x / (a + i);
11
+ if (Math.abs(result - previous) < EPSILON * result) break;
12
+ }
13
+ return Math.pow(z, a) * result;
14
+ };
15
+
16
+ export const beta = (a, b) => {
17
+ let result = 1;
18
+ for (let i = 1; i < LIMIT; ++i) {
19
+ const previous = result;
20
+ result *= (1 + (a + b) / i) / (1 + a / i) / (1 + b / i);
21
+ if (Math.abs(result - previous) < EPSILON * result) break;
22
+ }
23
+ return ((a + b) * result) / a / b;
24
+ };
25
+
26
+ export default beta;
@@ -0,0 +1,10 @@
1
+ import ppf from './ppf.js';
2
+
3
+ // percent point function
4
+ const chiSquaredPpf = (z, k) => {
5
+ const C = k / 2 - 1,
6
+ fn = z => Math.exp(-z / 2) * Math.pow(z, C);
7
+ return ppf(fn, z);
8
+ };
9
+
10
+ export default chiSquaredPpf;
@@ -0,0 +1,23 @@
1
+ const LIMIT = 1000;
2
+ const EPSILON = 1e-30;
3
+
4
+ const TWO_BY_SQRT_PI = 2 / Math.sqrt(Math.PI);
5
+
6
+ export const erf = z => {
7
+ let x = z,
8
+ result = x;
9
+ for (let i = 1; i < LIMIT; ++i) {
10
+ const previous = result;
11
+ x *= (-z * z) / i;
12
+ result += x / (2 * i + 1);
13
+ if (
14
+ (Math.abs(result) < EPSILON ? Math.abs(result - previous) : Math.abs(1 - previous / result)) <
15
+ EPSILON
16
+ ) {
17
+ break;
18
+ }
19
+ }
20
+ return TWO_BY_SQRT_PI * result;
21
+ };
22
+
23
+ export default erf;
@@ -0,0 +1,14 @@
1
+ import zeta from './zeta.js';
2
+
3
+ const LIMIT = 1000;
4
+ const EPSILON = 1e-30;
5
+
6
+ export const logGamma = z => {
7
+ let result = 0;
8
+ for (let i = 2; i < LIMIT; ++i) {
9
+ const previous = result;
10
+ result += ((i - 1) / i / (i + 1)) * zeta(i, z + 1);
11
+ if (Math.abs(result - previous) < EPSILON * result) break;
12
+ }
13
+ return (z - 0.5) * Math.log(z) - z + 0.5 * Math.log(2 * Math.PI) + 0.5 * result;
14
+ };
@@ -0,0 +1,19 @@
1
+ import {normalMakeCdf, normalMakePdf} from './normal.js';
2
+ import ppf from './ppf.js';
3
+
4
+ // percent point function
5
+ const normalPpf = (z, mu = 0, sigma = 1) => {
6
+ // find the lower bound
7
+ const cdf = normalMakeCdf(mu, sigma);
8
+ let x = mu - 6 * sigma,
9
+ p = cdf(x);
10
+ while (p > z) {
11
+ x = mu - (mu - x) * 2;
12
+ p = cdf(x);
13
+ }
14
+
15
+ const fn = normalMakePdf(mu, sigma);
16
+ return ppf(fn, z, {a: x, b: 2 * mu - x, initialValue: p});
17
+ };
18
+
19
+ export default normalPpf;
@@ -0,0 +1,25 @@
1
+ import erf from './erf.js';
2
+
3
+ const LIMIT = 1000;
4
+ const EPSILON = 1e-30;
5
+
6
+ const SQRT_2 = Math.sqrt(2),
7
+ SQRT_2_PI = Math.sqrt(2 * Math.PI);
8
+
9
+ export const normalCdf = (z, mu = 0, sigma = 1) => 0.5 * (1 + erf((z - mu) / sigma / SQRT_2));
10
+ export const normalMakeCdf =
11
+ (mu = 0, sigma = 1) =>
12
+ z =>
13
+ 0.5 * (1 + erf((z - mu) / sigma / SQRT_2));
14
+
15
+ export const normalPdf = (z, mu = 0, sigma = 1) => {
16
+ const x = (z - mu) / sigma;
17
+ return Math.exp(-0.5 * x * x) / sigma / SQRT_2_PI;
18
+ };
19
+
20
+ export const normalMakePdf =
21
+ (mu = 0, sigma = 1) =>
22
+ z => {
23
+ const x = (z - mu) / sigma;
24
+ return Math.exp(-0.5 * x * x) / sigma / SQRT_2_PI;
25
+ };
@@ -0,0 +1,18 @@
1
+ import {rk23} from '../utils/rk.js';
2
+ import bsearch from '../utils/bsearch.js';
3
+
4
+ // percent point function
5
+ const ppf = (fn, z, options) => {
6
+ const {ts, us, finalValue} = rk23(fn, options),
7
+ value = finalValue * z,
8
+ index = bsearch(us, x => x < value);
9
+ if (!index) return ts[0];
10
+ if (index >= us.length) return ts[ts.length - 1];
11
+ if (z == us[index]) return ts[index];
12
+ // linear interpolation
13
+ return (
14
+ ts[index] - ((us[index] - value) / (us[index] - us[index - 1])) * (ts[index] - ts[index - 1])
15
+ );
16
+ };
17
+
18
+ export default ppf;
@@ -0,0 +1,41 @@
1
+ export const getTotal = groups => groups.reduce((acc, group) => acc + group.length, 0);
2
+
3
+ export const rank = groups => {
4
+ const N = getTotal(groups),
5
+ k = groups.length,
6
+ t = new Array(N);
7
+
8
+ // put in one array preserving grouping
9
+ let o = 0;
10
+ for (let i = 0; i < k; ++i) {
11
+ const group = groups[i];
12
+ for (let j = 0; j < group.length; ++j) {
13
+ t[o++] = {value: group[j], group: i};
14
+ }
15
+ }
16
+
17
+ const groupRank = new Array(k);
18
+ groupRank.fill(0);
19
+
20
+ // sort and rank
21
+ t.sort((a, b) => a.value - b.value);
22
+ for (let i = 0; i < t.length; ) {
23
+ let ahead = i + 1;
24
+ const value = t[i].value;
25
+ while (ahead < t.length && value === t[ahead].value) ++ahead;
26
+ if (ahead - i === 1) {
27
+ groupRank[t[i].group] += t[i].rank = i + 1;
28
+ } else {
29
+ const rank = (i + 1 + ahead) / 2;
30
+ for (let j = i; j < ahead; ++j) {
31
+ groupRank[t[j].group] += t[j].rank = rank;
32
+ }
33
+ }
34
+ i = ahead;
35
+ }
36
+ const avgRank = (N + 1) / 2, avgGroupRank = groupRank.map((rank, i) => rank / groups[i].length);
37
+
38
+ return {ranked: t, N, k, avgRank, groupRank, avgGroupRank, groups};
39
+ };
40
+
41
+ export default rank;
@@ -0,0 +1,17 @@
1
+ import {zCdf, zPdf} from './z.js';
2
+ import ppf from './ppf.js';
3
+
4
+ // percent point function
5
+ const zPpf = (z) => {
6
+ // find the lower bound
7
+ let x = -6,
8
+ p = zCdf(x);
9
+ while (p > z) {
10
+ x = 2 * x;
11
+ p = zCdf(x);
12
+ }
13
+
14
+ return ppf(zPdf, z, {a: x, b: -x, initialValue: p});
15
+ };
16
+
17
+ export default zPpf;
package/src/stats/z.js ADDED
@@ -0,0 +1,7 @@
1
+ import erf from './erf.js';
2
+
3
+ const SQRT_2 = Math.sqrt(2),
4
+ SQRT_2_PI = Math.sqrt(2 * Math.PI);
5
+
6
+ export const zCdf = (z) => 0.5 * (1 + erf((z) / SQRT_2));
7
+ export const zPdf = (z) => Math.exp(-0.5 * z * z) / SQRT_2_PI;
@@ -0,0 +1,14 @@
1
+ const LIMIT = 1000;
2
+ const EPSILON = 1e-30;
3
+
4
+ export const zeta = (s, a) => {
5
+ let result = 0;
6
+ for (let i = 0; i < LIMIT; ++i) {
7
+ const previous = result;
8
+ result += 1 / Math.pow(i + a, s);
9
+ if (Math.abs(result - previous) < EPSILON * result) break;
10
+ }
11
+ return result;
12
+ };
13
+
14
+ export default zeta;