diff --git a/examples/stdlib/acme.effekt b/examples/stdlib/acme.effekt index 7ae412dfa..cffcd9178 100644 --- a/examples/stdlib/acme.effekt +++ b/examples/stdlib/acme.effekt @@ -24,6 +24,7 @@ import map import option import process import queue +import random import ref import regex import resizable_array diff --git a/examples/stdlib/random.check b/examples/stdlib/random.check new file mode 100644 index 000000000..cd10a502c --- /dev/null +++ b/examples/stdlib/random.check @@ -0,0 +1,65 @@ +prng +int32s: +-1472445053 +-935901669 +-1244218020 +492812375 +-894738723 +1372722888 +-1723450959 +2005696606 +536774910 +2111603542 +int32s, part2: +348632114 +-493311473 +521105902 +-441336655 +1315564179 +-245050234 +1432006216 +-2018660684 +349983049 +-1541851413 +1242068606 +-953174617 +728164170 +-558026150 +812040776 +-225070679 +125608749 +-1547184487 +2026319992 +-627925429 +doubles: +0.009 +0.758 +0.769 +0.032 +0.15 +0.118 +0.03 +0.946 +0.049 +0.565 +randomInt: +3 4 3 +343 +0 6 8 +68 +2 3 0 +230 +6 1 0 +610 +4 0 1 +401 +2 3 4 +234 +9 3 2 +932 +8 2 2 +822 +3 3 2 +332 +8 5 1 +851 diff --git a/examples/stdlib/random.effekt b/examples/stdlib/random.effekt new file mode 100644 index 000000000..c75109486 --- /dev/null +++ b/examples/stdlib/random.effekt @@ -0,0 +1,3 @@ +import random + +def main() = random::examples::main() diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 06e271db0..e34c6804c 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -344,7 +344,7 @@ extern pure def infixSub(x: Double, y: Double): Double = extern pure def infixDiv(x: Double, y: Double): Double = js "(${x} / ${y})" - chez "(/ ${x} ${y})" + chez "(fl/ ${x} ${y})" llvm "%z = fdiv %Double ${x}, ${y} ret %Double %z" vm "effekt::infixDiv(Double, Double)" @@ -442,14 +442,14 @@ extern pure def toInt(d: Double): Int = extern pure def toDouble(d: Int): Double = js "${d}" - chez "${d}" + chez "(fixnum->flonum ${d})" llvm "%z = sitofp i64 ${d} to double ret double %z" vm "effekt::toDouble(Int)" extern pure def round(d: Double): Int = js "Math.round(${d})" - chez "(round ${d})" + chez "(flonum->fixnum (round ${d}))" llvm """ %i = call %Double @llvm.round.f64(double ${d}) %z = fptosi double %i to %Int ret %Int %z diff --git a/libraries/common/random.effekt b/libraries/common/random.effekt new file mode 100644 index 000000000..b1998aec7 --- /dev/null +++ b/libraries/common/random.effekt @@ -0,0 +1,186 @@ +module random + +import stream +import io/error + +/// Infinite pull stream of random bytes. +effect random(): Byte + +// --------------------- +// Sources of randomness + +/// A streaming source (push stream) of byte-level randomness +/// based on Park and Miller's MINSTD with revised parameters. +/// +/// Deterministic: needs a 32bit `seed` -- you can use `bench::timestamp`. +def minstd(seed: Int): Unit / emit[Byte] = { + // Initialize state with seed, ensuring it's not zero + var state = if (seed == 0) 1 else seed + + def nextInt(): Int = { + // Uses only at most 32-bit integers internally + // (Schrage's method: https://en.wikipedia.org/wiki/Lehmer_random_number_generator#Schrage's_method) + val a = 48271 + val m = 2147483647 + + val q = m / a // 44488 + val r = m.mod(a) // 3399 + + val div = state / q // max: M / Q = A = 48271 + val rem = state.mod(q) // max: Q - 1 = 44487 + + val s = rem * a; // max: 44487 * 48271 = 2147431977 + val t = div * r; // max: 48271 * 3399 = 164073129 + + val result = s - t + // keep the state positive + if (result < 0) result + m else result + } + + while (true) { + state = nextInt() + val b = state.mod(256).toByte + do emit(b) + } +} + +/// A thin wrapper over `minstd`, handling a reader of random bytes. +/// +/// Deterministic: needs a 32bit `seed` -- you can use `bench::timestamp`. +/// +/// Implementation is similar to `stream::source`, specialized for bytes and the `random` effect. +def minstd(seed: Int) { randomnessReader: () => Unit / random }: Unit = { + var next = box { 255.toByte } // sentinel value + next = box { + try { + minstd(seed) + <> // safe: randomness generator cannot run out of numbers... + } with emit[Byte] { v => + next = box { resume(()) } + v + } + } + + try randomnessReader() with random { + resume(next()) + } +} + +/// CSPRNG from `/dev/urandom`, handling a reader of random bytes. +/// Only works on Unix-like OSes! +def devurandom { randomnessReader: () => Unit / random }: Unit / Exception[IOError] = + try { + with readFile("/dev/urandom") + try randomnessReader() with random { + resume(do read[Byte]()) + } + } with stop { + do raise(io::error::EOF(), "Unexpected EOF when reading /dev/urandom!") + } + +// ------------------------ +// Functions using `random` +// +// Always two variants: +// - readType(): Type / random +// - readTypes(): Unit / {emit[Type], random} + +def randomByte(): Byte / random = do random() +def randomBytes(): Unit / {emit[Byte], random} = + while (true) do emit(do random()) + +def randomBool(): Bool / random = { + val b = do random() + b.toInt.mod(2) == 1 +} +def randomBools(): Unit / {emit[Bool], random} = + while (true) do emit(randomBool()) + +def randomInt32(): Int / random = { + var result = 0 + repeat(4) { + val b = do random() + result = result * 256 + b.toInt + } + val signBit = result.bitwiseShr(31).bitwiseAnd(1) == 0 + result.mod(1.bitwiseShl(31)).abs * if (signBit) 1 else -1 +} +def randomInt32s(): Unit / {emit[Int], random} = + while (true) do emit(randomInt32()) + +/// `max` is _inclusive_! +def randomInt(min: Int, max: Int): Int / random = { + if (min > max) { + randomInt(max, min) + } else { + val range = max - min + 1 + val bytesNeeded = (log(range.toDouble) / log(256.0)).ceil + + var result = 0 + repeat(bytesNeeded) { + val b = do random() + result = result * 256 + b.toInt + } + + min + (abs(result).mod(range)) + } +} +/// `max` is _inclusive_! +def randomInts(min: Int, max: Int): Unit / {emit[Int], random} = + while (true) do emit(randomInt(min, max)) + + +/// Random double between 0.0 and 1.0 +def randomDouble(): Double / random = + (randomInt32().toDouble / 1.bitwiseShl(31).toDouble).abs + // This is not perfect, but it will do for now. +def randomDoubles(): Unit / {emit[Double], random} = + while (true) do emit(randomDouble()) + + +namespace examples { +def main() = { + println("prng") + prngRandom() + } + + def prngRandom(): Unit = { + with minstd(1337); + + println("int32s:") + repeat(10) { + println(randomInt32()) + } + + println("int32s, part2:") + repeat(10) { + println(randomInt(0, 2147483647)) + println(randomInt(-2147483648, 0)) + } + + println("doubles:") + repeat(10) { + println(randomDouble().round(3)) + } + + println("randomInt:") + repeat(10) { + val a = randomInt(0, 9) + val b = randomInt(0, 9) + val c = randomInt(0, 9) + println(a.show ++ " " ++ b.show ++ " " ++ c.show) + println(a*100 + b*10 + c) + } + } + + def unixRandom(): Unit = { + with on[IOError].report; + with devurandom; + + val a = randomInt32() + val b = randomInt32() + + // This is just to use the generated numbers :) + println((a.show ++ b.show).length != 0) + } +} \ No newline at end of file