@@ -2,6 +2,7 @@ import { fairlyAllocateCredit } from "./backend";
2
2
3
3
import { strict as assert } from "assert" ;
4
4
import test from "node:test" ;
5
+ import * as simplestatistics from "simple-statistics" ;
5
6
6
7
interface FairlyAllocateCreditTestCase {
7
8
name : string ;
@@ -14,7 +15,32 @@ function noRand(): number {
14
15
throw new Error ( "no rand expected" ) ;
15
16
}
16
17
17
- // TODO: Check that the distribution of credit arrays matches expectations.
18
+ type Interval = [ min : number , max : number ] ;
19
+
20
+ // https://en.wikipedia.org/wiki/Normal_distribution#Quantile_function
21
+ function ppf ( q : number , mean : number , stdDev : number ) : number {
22
+ return (
23
+ mean +
24
+ stdDev * Math . sqrt ( 2 ) * simplestatistics . inverseErrorFunction ( 2 * q - 1 )
25
+ ) ;
26
+ }
27
+
28
+ function getIntervalApprox ( n : number , p : number , alpha : number ) : Interval {
29
+ const mean = n * p ;
30
+ const variance = mean * ( 1 - p ) ;
31
+ const diff = ppf ( 1 - alpha / 2 , mean , Math . sqrt ( variance ) ) - mean ;
32
+ return [ mean - diff , mean + diff ] ;
33
+ }
34
+
35
+ function getAllIntervals (
36
+ n : number ,
37
+ creditFractions : readonly number [ ] ,
38
+ alphaTotal : number ,
39
+ ) : Interval [ ] {
40
+ const alpha = alphaTotal / creditFractions . length ;
41
+ return creditFractions . map ( ( cf ) => getIntervalApprox ( n , cf , alpha ) ) ;
42
+ }
43
+
18
44
function runFairlyAllocateCreditTest (
19
45
tc : Readonly < FairlyAllocateCreditTestCase > ,
20
46
) : void {
@@ -24,7 +50,9 @@ function runFairlyAllocateCreditTest(
24
50
( item ) => ( tc . value * item ) / sumCredit ,
25
51
) ;
26
52
27
- const [ rand , k ] = tc . needsRand ? [ Math . random , 10000 ] : [ noRand , 1 ] ;
53
+ const [ rand , k ] = tc . needsRand ? [ Math . random , 100000 ] : [ noRand , 1 ] ;
54
+
55
+ const totals = new Array < number > ( tc . credit . length ) . fill ( 0 ) ;
28
56
29
57
for ( let n = 0 ; n < k ; ++ n ) {
30
58
const actualCredit = fairlyAllocateCredit ( tc . credit , tc . value , rand ) ;
@@ -40,6 +68,8 @@ function runFairlyAllocateCreditTest(
40
68
diff <= 1 ,
41
69
`credit error > 1: actual=${ actual } , normalized=${ normalized } ` ,
42
70
) ;
71
+
72
+ totals [ j ] ! += actual / tc . value ;
43
73
}
44
74
45
75
assert . equal (
@@ -49,6 +79,19 @@ function runFairlyAllocateCreditTest(
49
79
`actual credit does not sum to value: ${ actualCredit . join ( ", " ) } ` ,
50
80
) ;
51
81
}
82
+
83
+ const intervals = getAllIntervals (
84
+ k ,
85
+ tc . credit . map ( ( c ) => c / sumCredit ) ,
86
+ 0.0001 ,
87
+ ) ;
88
+ for ( const [ j , total ] of totals . entries ( ) ) {
89
+ const [ min , max ] = intervals [ j ] ! ;
90
+ assert . ok (
91
+ total >= min && total <= max ,
92
+ `total for credit[${ j } ] ${ total } not in confidence interval [${ min } , ${ max } ]` ,
93
+ ) ;
94
+ }
52
95
}
53
96
54
97
const testCases : FairlyAllocateCreditTestCase [ ] = [
0 commit comments