1
+ module random
2
+
3
+ import stream
4
+ import io/error
5
+
6
+ /// Infinite pull stream of random bytes.
7
+ effect random(): Byte
8
+
9
+ // ---------------------
10
+ // Sources of randomness
11
+
12
+ /// A streaming source (push stream) of byte-level randomness
13
+ /// based on Park and Miller's MINSTD with revised parameters.
14
+ ///
15
+ /// Deterministic: needs a 32bit `seed` -- you can use `bench::timestamp`.
16
+ def minstd(seed: Int): Unit / emit[Byte] = {
17
+ // Initialize state with seed, ensuring it's not zero
18
+ var state = if (seed == 0) 1 else seed
19
+
20
+ def nextInt(): Int = {
21
+ // Uses only at most 32-bit integers internally
22
+ // (Schrage's method: https://en.wikipedia.org/wiki/Lehmer_random_number_generator#Schrage's_method)
23
+ val a = 48271
24
+ val m = 2147483647
25
+
26
+ val q = m / a // 44488
27
+ val r = m.mod(a) // 3399
28
+
29
+ val div = state / q // max: M / Q = A = 48271
30
+ val rem = state.mod(q) // max: Q - 1 = 44487
31
+
32
+ val s = rem * a; // max: 44487 * 48271 = 2147431977
33
+ val t = div * r; // max: 48271 * 3399 = 164073129
34
+
35
+ val result = s - t
36
+ // keep the state positive
37
+ if (result < 0) result + m else result
38
+ }
39
+
40
+ while (true) {
41
+ state = nextInt()
42
+ val b = state.mod(256).toByte
43
+ do emit(b)
44
+ }
45
+ }
46
+
47
+ /// A thin wrapper over `minstd`, handling a reader of random bytes.
48
+ ///
49
+ /// Deterministic: needs a 32bit `seed` -- you can use `bench::timestamp`.
50
+ ///
51
+ /// Implementation is similar to `stream::source`, specialized for bytes and the `random` effect.
52
+ def minstd(seed: Int) { randomnessReader: () => Unit / random }: Unit = {
53
+ var next = box { 255.toByte } // sentinel value
54
+ next = box {
55
+ try {
56
+ minstd(seed)
57
+ <> // safe: randomness generator cannot run out of numbers...
58
+ } with emit[Byte] { v =>
59
+ next = box { resume(()) }
60
+ v
61
+ }
62
+ }
63
+
64
+ try randomnessReader() with random {
65
+ resume(next())
66
+ }
67
+ }
68
+
69
+ /// CSPRNG from `/dev/urandom`, handling a reader of random bytes.
70
+ /// Only works on Unix-like OSes!
71
+ def devurandom { randomnessReader: () => Unit / random }: Unit / Exception[IOError] =
72
+ try {
73
+ with readFile("/dev/urandom")
74
+ try randomnessReader() with random {
75
+ resume(do read[Byte]())
76
+ }
77
+ } with stop {
78
+ do raise(io::error::EOF(), "Unexpected EOF when reading /dev/urandom!")
79
+ }
80
+
81
+ // ------------------------
82
+ // Functions using `random`
83
+ //
84
+ // Always two variants:
85
+ // - readType(): Type / random
86
+ // - readTypes(): Unit / {emit[Type], random}
87
+
88
+ def randomByte(): Byte / random = do random()
89
+ def randomBytes(): Unit / {emit[Byte], random} =
90
+ while (true) do emit(do random())
91
+
92
+ def randomBool(): Bool / random = {
93
+ val b = do random()
94
+ b.toInt.mod(2) == 1
95
+ }
96
+ def randomBools(): Unit / {emit[Bool], random} =
97
+ while (true) do emit(randomBool())
98
+
99
+ def randomInt32(): Int / random = {
100
+ var result = 0
101
+ repeat(4) {
102
+ val b = do random()
103
+ result = result * 256 + b.toInt
104
+ }
105
+ val signBit = result.bitwiseShr(31).bitwiseAnd(1) == 0
106
+ result.mod(1.bitwiseShl(31)).abs * if (signBit) 1 else -1
107
+ }
108
+ def randomInt32s(): Unit / {emit[Int], random} =
109
+ while (true) do emit(randomInt32())
110
+
111
+ /// `max` is _inclusive_!
112
+ def randomInt(min: Int, max: Int): Int / random = {
113
+ if (min > max) {
114
+ randomInt(max, min)
115
+ } else {
116
+ val range = max - min + 1
117
+ val bytesNeeded = (log(range.toDouble) / log(256.0)).ceil
118
+
119
+ var result = 0
120
+ repeat(bytesNeeded) {
121
+ val b = do random()
122
+ result = result * 256 + b.toInt
123
+ }
124
+
125
+ min + (abs(result).mod(range))
126
+ }
127
+ }
128
+ /// `max` is _inclusive_!
129
+ def randomInts(min: Int, max: Int): Unit / {emit[Int], random} =
130
+ while (true) do emit(randomInt(min, max))
131
+
132
+
133
+ /// Random double between 0.0 and 1.0
134
+ def randomDouble(): Double / random =
135
+ (randomInt32().toDouble / 1.bitwiseShl(31).toDouble).abs
136
+ // This is not perfect, but it will do for now.
137
+ def randomDoubles(): Unit / {emit[Double], random} =
138
+ while (true) do emit(randomDouble())
139
+
140
+
141
+ namespace examples {
142
+ def main() = {
143
+ println("prng")
144
+ prngRandom()
145
+ }
146
+
147
+ def prngRandom(): Unit = {
148
+ with minstd(1337);
149
+
150
+ println("int32s:")
151
+ repeat(10) {
152
+ println(randomInt32())
153
+ }
154
+
155
+ println("int32s, part2:")
156
+ repeat(10) {
157
+ println(randomInt(0, 2147483647))
158
+ println(randomInt(-2147483648, 0))
159
+ }
160
+
161
+ println("doubles:")
162
+ repeat(10) {
163
+ println(randomDouble().round(3))
164
+ }
165
+
166
+ println("randomInt:")
167
+ repeat(10) {
168
+ val a = randomInt(0, 9)
169
+ val b = randomInt(0, 9)
170
+ val c = randomInt(0, 9)
171
+ println(a.show ++ " " ++ b.show ++ " " ++ c.show)
172
+ println(a*100 + b*10 + c)
173
+ }
174
+ }
175
+
176
+ def unixRandom(): Unit = {
177
+ with on[IOError].report;
178
+ with devurandom;
179
+
180
+ val a = randomInt32()
181
+ val b = randomInt32()
182
+
183
+ // This is just to use the generated numbers :)
184
+ println((a.show ++ b.show).length != 0)
185
+ }
186
+ }
0 commit comments