formualizer_eval/
rng.rs

1//! Deterministic RNG helpers for functions
2use rand::{SeedableRng, rngs::SmallRng};
3
4/// Stable 64-bit FNV-1a hash (deterministic across platforms)
5#[inline]
6pub fn fnv1a64(data: &[u8]) -> u64 {
7    const OFFSET: u64 = 0xcbf29ce484222325;
8    const PRIME: u64 = 0x00000100000001B3;
9    let mut hash = OFFSET;
10    for b in data {
11        hash ^= *b as u64;
12        hash = hash.wrapping_mul(PRIME);
13    }
14    hash
15}
16
17#[inline]
18pub fn mix64(a: u64, b: u64) -> u64 {
19    // Simple reversible mix (xorshift-based)
20    let mut x = a ^ (b.rotate_left(17));
21    x ^= x >> 33;
22    x = x.wrapping_mul(0xff51afd7ed558ccd);
23    x ^= x >> 33;
24    x = x.wrapping_mul(0xc4ceb9fe1a85ec53);
25    x ^ (x >> 33)
26}
27
28/// Compose a deterministic 128-bit seed from components.
29/// Returns two u64 lanes suitable to seed SmallRng.
30pub fn compose_seed(
31    workbook_seed: u64,
32    sheet_id: u32,
33    row: u32,
34    col: u32,
35    fn_salt: u64,
36    recalc_epoch: u64,
37) -> (u64, u64) {
38    let pos = ((sheet_id as u64) << 40) ^ ((row as u64) << 20) ^ (col as u64);
39    // Mix epoch into both lanes to guarantee effect on the first outputs
40    let lane0 = mix64(workbook_seed ^ fn_salt ^ recalc_epoch, pos);
41    let lane1 = mix64(recalc_epoch ^ 0xA5A5_A5A5_5A5A_5A5A_u64, pos.rotate_left(7));
42    (lane0, lane1)
43}
44
45/// Build a SmallRng from the two seed lanes
46pub fn small_rng_from_lanes(l0: u64, l1: u64) -> SmallRng {
47    // Use a portable seed derivation regardless of SmallRng's internal Seed size
48    let s = mix64(l0, l1);
49    SmallRng::seed_from_u64(s)
50}