fix(fsharp): mulberry32 wraps through uint64 so the Fable build matches the fixtures
Fable compiles uint32 * and + to plain JavaScript arithmetic (no wrap, inexact above 2^53); 2 of 37 RNG fixture cases matched in the browser build. uint64 helpers are exact on .NET and in Fable (BigInt). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ab8abaaa77
commit
527b2cd157
@@ -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<uint32> =
|
||||
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<int> = map (fun v -> int (v % uint32 n)) nextUint32
|
||||
|
||||
Reference in New Issue
Block a user