pub(crate) struct SplitMix64(u64);
impl SplitMix64 {
pub(crate) fn new(seed: u64) -> Self {
Self(seed)
}
pub(crate) fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
pub(crate) fn next_unit_f64(&mut self) -> f64 {
#[allow(clippy::cast_precision_loss)]
let mantissa = (self.next_u64() >> 11) as f64;
mantissa * 2.0_f64.powi(-53)
}
}
pub(crate) fn fnv1a64(bytes: &[u8]) -> u64 {
let mut hash = 0xcbf2_9ce4_8422_2325_u64;
for &b in bytes {
hash ^= u64::from(b);
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
hash
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::float_cmp
)]
mod tests {
use super::{SplitMix64, fnv1a64};
fn template_next_u64(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[test]
fn next_u64_matches_template_sequence() {
const SEED: u64 = 0x1234_5678_9abc_def0;
let mut rng = SplitMix64::new(SEED);
let mut template_state = SEED;
for i in 0..64 {
let got = rng.next_u64();
let want = template_next_u64(&mut template_state);
assert_eq!(
got, want,
"production SplitMix64 diverged from the template at draw {i}"
);
}
}
#[test]
fn next_unit_f64_is_in_half_open_unit_interval() {
let mut rng = SplitMix64::new(0xdead_beef_cafe_f00d);
for _ in 0..10_000 {
let u = rng.next_unit_f64();
assert!(u >= 0.0, "next_unit_f64 must be >= 0.0, got {u}");
assert!(u < 1.0, "next_unit_f64 must be < 1.0, got {u}");
}
}
#[test]
fn same_seed_yields_same_stream() {
let mut a = SplitMix64::new(42);
let mut b = SplitMix64::new(42);
for _ in 0..256 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn fnv1a64_is_deterministic_and_sensitive() {
let a = fnv1a64(&[1, 2, 3, 4]);
let b = fnv1a64(&[1, 2, 3, 4]);
assert_eq!(a, b, "fnv1a64 must be deterministic");
let c = fnv1a64(&[1, 2, 3, 5]);
assert_ne!(a, c, "a one-byte change must perturb the hash");
}
#[test]
fn fnv1a64_matches_reference_offset_basis() {
assert_eq!(
fnv1a64(&[]),
0xcbf2_9ce4_8422_2325_u64,
"empty input must hash to the FNV-1a offset basis"
);
let expected_zero = 0xcbf2_9ce4_8422_2325_u64.wrapping_mul(0x0000_0100_0000_01b3);
assert_eq!(
fnv1a64(&[0]),
expected_zero,
"a single zero byte must apply exactly one prime multiply"
);
}
}