diff --git a/fsharp/solutions/Random.fs b/fsharp/solutions/Random.fs index 737c63a..3e0ec58 100644 --- a/fsharp/solutions/Random.fs +++ b/fsharp/solutions/Random.fs @@ -19,13 +19,19 @@ let bind (f: 'a -> Random<'b>) (r: Random<'a>) : Random<'b> = let a, next = run seed r run next (f a)) -// mulberry32. uint32 arithmetic wraps around at 2^32 (F# operators are unchecked) and `>>>` on uint32 is an -// unsigned shift, so the formula can be written down as it is. +// 32-bit wrap-around `*` and `+`. On .NET plain uint32 `*` and `+` already wrap at 2^32, but Fable (the F# to +// JavaScript compiler behind the browser page) compiles them to plain JavaScript `*` and `+`, which neither wrap nor +// stay exact for big products. Going through uint64 is exact on both: the product of two uint32 values fits in a +// uint64, and converting back to uint32 keeps the low 32 bits. +let private mul (a: uint32) (b: uint32) : uint32 = uint32 (uint64 a * uint64 b) +let private add (a: uint32) (b: uint32) : uint32 = uint32 (uint64 a + uint64 b) + +// mulberry32. `^^^`, `|||` and `>>>` (an unsigned shift on uint32) behave the same everywhere. let nextUint32: Random = Random(fun seed -> - let s = seed + 0x6D2B79F5u - let t1 = (s ^^^ (s >>> 15)) * (s ||| 1u) - let t2 = t1 ^^^ (t1 + (t1 ^^^ (t1 >>> 7)) * (t1 ||| 61u)) + let s = add seed 0x6D2B79F5u + let t1 = mul (s ^^^ (s >>> 15)) (s ||| 1u) + let t2 = t1 ^^^ add t1 (mul (t1 ^^^ (t1 >>> 7)) (t1 ||| 61u)) (t2 ^^^ (t2 >>> 14), s)) let intBelow (n: int) : Random = map (fun v -> int (v % uint32 n)) nextUint32 diff --git a/fsharp/src/Random.fs b/fsharp/src/Random.fs index 230707d..8cf83a9 100644 --- a/fsharp/src/Random.fs +++ b/fsharp/src/Random.fs @@ -17,12 +17,14 @@ let map (f: 'a -> 'b) (r: Random<'a>) : Random<'b> = failwith "TODO m04" /// Run `r`, feed its value to `f` to pick the next Random, and run that with the seed `r` left behind. let bind (f: 'a -> Random<'b>) (r: Random<'a>) : Random<'b> = failwith "TODO m04" -/// The generator (mulberry32). uint32 arithmetic already wraps around at 2^32 and `>>>` on a uint32 is an -/// unsigned shift. With `s = seed + 0x6D2B79F5u`: +/// The generator (mulberry32). With `s = seed + 0x6D2B79F5u`: /// t = (s ^^^ (s >>> 15)) * (s ||| 1u) /// t = t ^^^ (t + (t ^^^ (t >>> 7)) * (t ||| 61u)) /// value = t ^^^ (t >>> 14) -/// It returns `value` and the new seed `s`. +/// It returns `value` and the new seed `s`. Every `*` and `+` must wrap around at 2^32, and `>>>` on a uint32 is an +/// unsigned shift. On .NET uint32 `*` and `+` already wrap, but the browser build (Fable) turns them into plain +/// JavaScript arithmetic that does not, so write them as small helpers that go through uint64 and back, e.g. +/// `let mul (a: uint32) (b: uint32) = uint32 (uint64 a * uint64 b)`; that is exact on both. /// (The stub is wrapped in `Random` so the TODO only fires when the generator is run.) let nextUint32: Random = Random(fun _ -> failwith "TODO m04")