@@ -4,6 +4,57 @@ open System
44open Util.Testing
55open Fable.Tests .Util
66
7+ let chiSquaredStatistic bucketCount sampleCount ( nextBucket : unit -> int ) =
8+ let counts = Array.zeroCreate bucketCount
9+
10+ for _ in 1 .. sampleCount do
11+ let bucket = nextBucket()
12+ counts[ bucket] <- counts[ bucket] + 1
13+
14+ let expected = float sampleCount / float bucketCount
15+
16+ counts
17+ |> Array.sumBy ( fun count ->
18+ let diff = float count - expected
19+ diff * diff / expected)
20+
21+ let chiSquaredUpperBound degreesOfFreedom sigma =
22+ float degreesOfFreedom + sigma * Math.Sqrt( 2.0 * float degreesOfFreedom)
23+
24+ let runsZScore sampleCount ( nextBit : unit -> bool ) =
25+ let mutable ones = 0
26+ let mutable zeros = 0
27+ let mutable runs = 0
28+ let mutable hasPrevious = false
29+ let mutable previous = false
30+
31+ for _ in 1 .. sampleCount do
32+ let current = nextBit()
33+
34+ if current then
35+ ones <- ones + 1
36+ else
37+ zeros <- zeros + 1
38+
39+ if not hasPrevious || current <> previous then
40+ runs <- runs + 1
41+ previous <- current
42+ hasPrevious <- true
43+
44+ if ones = 0 || zeros = 0 then
45+ Double.PositiveInfinity
46+ else
47+ let ones ' = float ones
48+ let zeros ' = float zeros
49+ let total = float sampleCount
50+ let expectedRuns = 1.0 + 2.0 * ones' * zeros' / total
51+
52+ let variance =
53+ ( 2.0 * ones' * zeros' * ( 2.0 * ones' * zeros' - ones' - zeros'))
54+ / ( total * total * ( total - 1.0 ))
55+
56+ Math.Abs( float runs - expectedRuns) / Math.Sqrt( variance)
57+
758let tests =
859 testList " Random" [
960 testCase " System.Random works" <| fun () ->
@@ -33,6 +84,16 @@ let tests =
3384 throwsAnyError <| fun () -> rnd.Next( 14 , 10 )
3485 throwsAnyError <| fun () -> rnd.NextBytes( null )
3586
87+ testCase " System.Random seeded passes chi-squared test" <| fun () ->
88+ let rnd = Random( 2026 )
89+ let statistic = chiSquaredStatistic 16 10000 ( fun () -> rnd.Next( 16 ))
90+ statistic < chiSquaredUpperBound 15 4.0 |> equal true
91+
92+ testCase " System.Random seeded passes Wald-Wolfowitz runs test" <| fun () ->
93+ let rnd = Random( 2027 )
94+ let zScore = runsZScore 10000 ( fun () -> rnd.NextDouble() >= 0.5 )
95+ zScore < 4.0 |> equal true
96+
3697 // Note: Test could fail sometime during life of universe, if it picks all zeroes.
3798 testCase " System.Random.NextBytes works" <| fun () ->
3899 let buffer = Array.create 16 0 uy // guid-sized buffer
0 commit comments