pub fn hash_unit(a: i64, b: i64, seed: i64) -> f64 {
let mut x = (a as u64).wrapping_mul(0x9E3779B97F4A7C15);
x ^= (b as u64).wrapping_mul(0xC2B2AE3D27D4EB4F);
x ^= (seed as u64).wrapping_mul(0x165667B19E3779F9);
x = (x ^ (x >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
x = (x ^ (x >> 27)).wrapping_mul(0x94D049BB133111EB);
x ^= x >> 31;
((x >> 11) as f64) / ((1u64 << 53) as f64)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deterministic_same_inputs_same_output() {
for (a, b, s) in [(0, 0, 0), (1, 2, 3), (-5, 7, 42), (i64::MAX, i64::MIN, 9)] {
assert_eq!(hash_unit(a, b, s), hash_unit(a, b, s));
}
}
#[test]
fn always_in_unit_range() {
for a in -50..50 {
for b in -3..3 {
let v = hash_unit(a, b, a ^ b);
assert!((0.0..1.0).contains(&v), "out of range: {v} for ({a},{b})");
}
}
assert!((0.0..1.0).contains(&hash_unit(i64::MAX, i64::MIN, i64::MAX)));
assert!((0.0..1.0).contains(&hash_unit(i64::MIN, i64::MAX, i64::MIN)));
}
#[test]
fn different_inputs_differ() {
let base = hash_unit(0, 0, 0);
assert_ne!(base, hash_unit(1, 0, 0));
assert_ne!(base, hash_unit(0, 1, 0));
assert_ne!(base, hash_unit(0, 0, 1));
assert_ne!(hash_unit(2, 3, 7), hash_unit(2, 3, 7 ^ 0x5555));
assert_ne!(hash_unit(1, 2, 0), hash_unit(2, 1, 0));
}
#[test]
fn known_values_lock_determinism() {
assert_eq!(hash_unit(0, 0, 0), 0.0);
let v100 = hash_unit(1, 0, 0);
let v010 = hash_unit(0, 1, 0);
let v123 = hash_unit(1, 2, 3);
assert_eq!(v100, hash_unit(1, 0, 0));
assert_eq!(v010, hash_unit(0, 1, 0));
assert_eq!(v123, hash_unit(1, 2, 3));
for v in [v100, v010, v123] {
assert!((0.0..1.0).contains(&v));
}
assert_ne!(v100, v010);
}
}