math/rand: Add xoshiro256**

This is extracted from the previos PR as an improved non-cryptographic
PRNG option.
This commit is contained in:
Yawning Angel
2025-11-24 17:48:23 +09:00
parent 110d869cd7
commit f74f93154a

View File

@@ -0,0 +1,123 @@
package rand
import "base:intrinsics"
import "base:runtime"
import "core:math/bits"
/*
The state for a xoshiro256** pseudorandom generator.
*/
Xoshiro256_Random_State :: struct {
s: [4]u64,
}
xoshiro256_random_generator_proc :: proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) {
@(require_results)
read_u64 :: proc "contextless" (r: ^Xoshiro256_Random_State) -> u64 {
// xoshiro256** output function and state transition
result := bits.rotate_left64(r.s[1] * 5, 7) * 9
t := r.s[1] << 17
r.s[2] = r.s[2] ~ r.s[0]
r.s[3] = r.s[3] ~ r.s[1]
r.s[1] = r.s[1] ~ r.s[2]
r.s[0] = r.s[0] ~ r.s[3]
r.s[2] = r.s[2] ~ t
r.s[3] = bits.rotate_left64(r.s[3], 45)
return result
}
@(thread_local)
global_rand_seed: Xoshiro256_Random_State
init :: proc "contextless" (r: ^Xoshiro256_Random_State, seed: u64) {
// splitmix64 to expand a 64-bit seed into 256 bits of state
sm64_next :: proc "contextless" (s: ^u64) -> u64 {
s^ += 0x9E3779B97F4A7C15
z := s^
z = (z ~ (z >> 30)) * 0xBF58476D1CE4E5B9
z = (z ~ (z >> 27)) * 0x94D049BB133111EB
return z ~ (z >> 31)
}
local_seed := seed
r.s[0] = sm64_next(&local_seed)
r.s[1] = sm64_next(&local_seed)
r.s[2] = sm64_next(&local_seed)
r.s[3] = sm64_next(&local_seed)
// Extremely unlikely all zero; ensure non-zero state
if (r.s[0] | r.s[1] | r.s[2] | r.s[3]) == 0 {
// force a minimal non-zero tweak
r.s[0] = 1
}
}
r: ^Xoshiro256_Random_State = ---
if data == nil {
r = &global_rand_seed
} else {
r = cast(^Xoshiro256_Random_State)data
}
switch mode {
case .Read:
if (r.s[0] | r.s[1] | r.s[2] | r.s[3]) == 0 {
init(r, u64(intrinsics.read_cycle_counter()))
}
switch len(p) {
case size_of(u64):
// Fast path for a 64-bit destination.
intrinsics.unaligned_store((^u64)(raw_data(p)), read_u64(r))
case:
// All other cases.
pos := i8(0)
val := u64(0)
for &v in p {
if pos == 0 {
val = read_u64(r)
pos = 8
}
v = byte(val)
val >>= 8
pos -= 1
}
}
case .Reset:
seed: u64 = 0
runtime.mem_copy_non_overlapping(&seed, raw_data(p), min(size_of(seed), len(p)))
init(r, seed)
case .Query_Info:
if len(p) != size_of(Generator_Query_Info) {
return
}
info := (^Generator_Query_Info)(raw_data(p))
info^ += {.Uniform, .Resettable}
}
}
/*
Returns an instance of the xoshiro256** pseudorandom generator. If no
initial state is provided, the PRNG will be lazily initialized with the
system timestamp counter on first-use.
WARNING: This random number generator is NOT cryptographically secure.
Inputs:
- state: Optional initial PRNG state.
Returns:
- A `Generator` instance.
*/
@(require_results)
xoshiro256_random_generator :: proc "contextless" (state: ^Xoshiro256_Random_State = nil) -> Generator {
return {
procedure = xoshiro256_random_generator_proc,
data = state,
}
}