Skip to content

Commit 8152c6b

Browse files
authored
Add bytestream-based 'random' module with PRNG and /dev/urandom handlers & fix float ops on Chez (#966)
Straightforward streaming randomness with an interface supporting both bytes-based PRNG and /dev/urandom.
1 parent dcb00cd commit 8152c6b

File tree

5 files changed

+258
-3
lines changed

5 files changed

+258
-3
lines changed

examples/stdlib/acme.effekt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import map
2424
import option
2525
import process
2626
import queue
27+
import random
2728
import ref
2829
import regex
2930
import resizable_array

examples/stdlib/random.check

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
prng
2+
int32s:
3+
-1472445053
4+
-935901669
5+
-1244218020
6+
492812375
7+
-894738723
8+
1372722888
9+
-1723450959
10+
2005696606
11+
536774910
12+
2111603542
13+
int32s, part2:
14+
348632114
15+
-493311473
16+
521105902
17+
-441336655
18+
1315564179
19+
-245050234
20+
1432006216
21+
-2018660684
22+
349983049
23+
-1541851413
24+
1242068606
25+
-953174617
26+
728164170
27+
-558026150
28+
812040776
29+
-225070679
30+
125608749
31+
-1547184487
32+
2026319992
33+
-627925429
34+
doubles:
35+
0.009
36+
0.758
37+
0.769
38+
0.032
39+
0.15
40+
0.118
41+
0.03
42+
0.946
43+
0.049
44+
0.565
45+
randomInt:
46+
3 4 3
47+
343
48+
0 6 8
49+
68
50+
2 3 0
51+
230
52+
6 1 0
53+
610
54+
4 0 1
55+
401
56+
2 3 4
57+
234
58+
9 3 2
59+
932
60+
8 2 2
61+
822
62+
3 3 2
63+
332
64+
8 5 1
65+
851

examples/stdlib/random.effekt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import random
2+
3+
def main() = random::examples::main()

libraries/common/effekt.effekt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ extern pure def infixSub(x: Double, y: Double): Double =
344344

345345
extern pure def infixDiv(x: Double, y: Double): Double =
346346
js "(${x} / ${y})"
347-
chez "(/ ${x} ${y})"
347+
chez "(fl/ ${x} ${y})"
348348
llvm "%z = fdiv %Double ${x}, ${y} ret %Double %z"
349349
vm "effekt::infixDiv(Double, Double)"
350350

@@ -442,14 +442,14 @@ extern pure def toInt(d: Double): Int =
442442

443443
extern pure def toDouble(d: Int): Double =
444444
js "${d}"
445-
chez "${d}"
445+
chez "(fixnum->flonum ${d})"
446446
llvm "%z = sitofp i64 ${d} to double ret double %z"
447447
vm "effekt::toDouble(Int)"
448448

449449

450450
extern pure def round(d: Double): Int =
451451
js "Math.round(${d})"
452-
chez "(round ${d})"
452+
chez "(flonum->fixnum (round ${d}))"
453453
llvm """
454454
%i = call %Double @llvm.round.f64(double ${d})
455455
%z = fptosi double %i to %Int ret %Int %z

libraries/common/random.effekt

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
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

Comments
 (0)