const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
pub fn fnv1a_64(bytes: &[u8]) -> u64 {
let mut hash = FNV_OFFSET_BASIS;
for &byte in bytes {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(FNV_PRIME);
}
fmix64(hash)
}
fn fmix64(mut h: u64) -> u64 {
h ^= h >> 33;
h = h.wrapping_mul(0xff51_afd7_ed55_8ccd);
h ^= h >> 33;
h = h.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
h ^= h >> 33;
h
}
#[cfg(test)]
mod tests {
use super::fnv1a_64;
use std::collections::HashSet;
#[test]
fn deterministic() {
assert_eq!(fnv1a_64(b"my_island_42"), fnv1a_64(b"my_island_42"));
}
#[test]
fn no_collisions_on_near_identical_inputs() {
let mut seen = HashSet::new();
for line in 0..1_000u32 {
for col in 0..32u32 {
let key = format!("{line}:{col}:src/app.rs");
assert!(
seen.insert(fnv1a_64(key.as_bytes())),
"collision for {key}"
);
}
}
}
#[test]
fn single_byte_change_avalanches() {
let a = fnv1a_64(b"component_a");
let b = fnv1a_64(b"component_b");
assert!((a ^ b).count_ones() >= 16, "weak avalanche: {a:x} vs {b:x}");
}
}