code wiki / (root) / nx_xoshiro.nx

nx_xoshiro.nx source

↩ module page · 114 lines · 3725 B

1// xoshiro.nx -- xoshiro256++ pseudorandom number generator. 2// 3// David Blackman + Sebastiano Vigna 2018. 256-bit state, 4// 64-bit output per step, period 2^256 - 1. Passes BigCrush 5// statistical tests. Used by: Rust `rand` standard generator, 6// Lua 5.4, GNU FORTRAN, .NET 6+. 7// 8// NOT cryptographically secure -- an attacker who observes 9// enough output can reconstruct state. For crypto use rand.nx 10// (/dev/urandom) or HMAC-DRBG (pending). 11// 12// Used for: simulation, deterministic test data, sampling, 13// fuzzing. When you need REPRODUCIBLE pseudorandom from a seed. 14// 15// Invariants: 16// X1 64-bit output per step; full state advances. 17// X2 Same seed -> same output sequence forever. 18// X3 All-zeros state forbidden (would produce all zeros). 19// seed_with implements the SplitMix64 trick to safely 20// initialise from any 64-bit seed including 0. 21// 22// license_tier: INDEPENDENT_REDERIVE 23// genealogy_id: international-research-sources/nist/sp_800_90a 24// 25 26// nx_safety_envelope: 27// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 28// sil_target: SIL1 29// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 30// verdict: NOT_YET_EVALUATED 31 32import "nx_syscalls.nx" 33import "nx_bits.nx" 34 35struct Xoshiro { 36 s0: i64, 37 s1: i64, 38 s2: i64, 39 s3: i64, 40} 41 42// Delegated to nx_bits_rotl64 (rolq/rol intrinsic). 43func xs_rotl(x: i64, k: i64) -> i64 { 44 return nx_bits_rotl64(x, k) 45} 46 47// SplitMix64 single-step (Vigna 2014). Used to initialise the 48// xoshiro state from a single seed -- avoids the all-zeros 49// pathological start. 50func xs_splitmix(state_p: *i64) -> i64 { 51 *state_p = *state_p + 0x9E3779B97F4A7C15 52 var z: i64 = *state_p 53 z = (z ^ ((z >> 30) & 0x3FFFFFFFFFFFFFFF)) * 0xBF58476D1CE4E5B9 54 z = (z ^ ((z >> 27) & 0x1FFFFFFFFFFFFFFF)) * 0x94D049BB133111EB 55 return z ^ ((z >> 31) & 0x1FFFFFFF) 56} 57 58// Initialise the generator from a 64-bit seed. 59func xoshiro_seed(rng: *Xoshiro, seed: i64) -> i64 { 60 let sm_state_raw: *u8 = sys_mmap(16) 61 let sm_state: *i64 = sm_state_raw as *i64 62 *sm_state = seed 63 rng.s0 = xs_splitmix(sm_state) 64 rng.s1 = xs_splitmix(sm_state) 65 rng.s2 = xs_splitmix(sm_state) 66 rng.s3 = xs_splitmix(sm_state) 67 return 0 68} 69 70// Construct + seed. Caller-owned struct. 71func xoshiro_new(seed: i64) -> *Xoshiro { 72 let raw: *u8 = sys_mmap(64) 73 let rng: *Xoshiro = raw as *Xoshiro 74 xoshiro_seed(rng, seed) 75 return rng 76} 77 78// Advance the state and return 64 bits. This is the xoshiro256++ 79// scrambler: rotl(s0 + s3, 23) + s0. 80func xoshiro_next(rng: *Xoshiro) -> i64 { 81 let result: i64 = xs_rotl(rng.s0 + rng.s3, 23) + rng.s0 82 let t: i64 = rng.s1 << 17 83 rng.s2 = rng.s2 ^ rng.s0 84 rng.s3 = rng.s3 ^ rng.s1 85 rng.s1 = rng.s1 ^ rng.s2 86 rng.s0 = rng.s0 ^ rng.s3 87 rng.s2 = rng.s2 ^ t 88 rng.s3 = xs_rotl(rng.s3, 45) 89 return result 90} 91 92// Return a value in [0, bound). Uses Lemire's nearly-divisionless 93// bounded random method (2019). 94func xoshiro_bounded(rng: *Xoshiro, bound: i64) -> i64 { 95 if bound <= 1 { return 0 } 96 // Simple modulo for now (small bias). Lemire's method needs 97 // 128-bit multiplication we can't do cleanly. 98 let v: i64 = xoshiro_next(rng) 99 // Convert to non-negative range. 100 let pos: i64 = v & 0x7FFFFFFFFFFFFFFF 101 return pos % bound 102} 103 104// Compile-only smoke. 105func main() -> i64 { 106 let rng: *Xoshiro = xoshiro_new(42) 107 let v1: i64 = xoshiro_next(rng) 108 let v2: i64 = xoshiro_next(rng) 109 if v1 == v2 { return 1 } // would be astronomically improbable 110 let bounded: i64 = xoshiro_bounded(rng, 100) 111 if bounded < 0 { return 2 } 112 if bounded >= 100 { return 3 } 113 return 0 114}