const_fnv1a_hash/
lib.rs

1#![no_std]
2
3const FNV_OFFSET_BASIS_32:  u32  = 0x811c9dc5;
4const FNV_OFFSET_BASIS_64:  u64  = 0xcbf29ce484222325;
5const FNV_OFFSET_BASIS_128: u128 = 0x6c62272e07bb014262b821756295c58d;
6
7const FNV_PRIME_32:  u32  = 0x01000193;
8const FNV_PRIME_64:  u64  = 0x00000100000001B3;
9const FNV_PRIME_128: u128 = 0x0000000001000000000000000000013B;
10
11macro_rules! fnv_hash_impl {
12    ($typ:ty, $size:literal, $fn_name:ident, $str_fn_name:ident, $offset:expr, $prime:expr) => {
13        #[doc = concat![
14            "Computes ",
15            stringify!($size),
16            "-bits fnv1a hash of the given slice, or up-to limit if provided. ",
17            "If limit is zero or exceeds slice length, slice length is used instead.",
18        ]]
19        pub const fn $fn_name(bytes: &[u8], limit: Option<usize>) -> $typ {
20            let prime = $prime;
21
22            let mut hash = $offset;
23            let mut i = 0;
24            let len = match limit {
25                Some(v) if 0 < v && v <= bytes.len() => {
26                    v
27                },
28                _ => {
29                    bytes.len()
30                }
31            };
32
33            while i < len {
34                hash ^= bytes[i] as $typ;
35                hash = hash.wrapping_mul(prime);
36                i += 1;
37            }
38            hash
39        }
40
41        #[doc = concat![
42            "Computes ",
43            stringify!($size),
44            "-bit fnv1a hash from a str."
45        ]]
46        #[inline(always)]
47        pub const fn $str_fn_name(input: &str) -> $typ {
48            $fn_name(input.as_bytes(), None)
49        }
50    }
51}
52
53fnv_hash_impl!{u32,  32,  fnv1a_hash_32,  fnv1a_hash_str_32,  FNV_OFFSET_BASIS_32,  FNV_PRIME_32}
54fnv_hash_impl!{u64,  64,  fnv1a_hash_64,  fnv1a_hash_str_64,  FNV_OFFSET_BASIS_64,  FNV_PRIME_64}
55fnv_hash_impl!{u128, 128, fnv1a_hash_128, fnv1a_hash_str_128, FNV_OFFSET_BASIS_128, FNV_PRIME_128}
56
57/// Computes 32-bits fnv1a hash and XORs higher and lower 16-bits.
58/// This results in a 16-bits hash value.
59/// Up to limit if provided, otherwise slice length.
60/// If limit is zero or exceeds slice length, slice length is used instead.
61#[inline(always)]
62pub const fn fnv1a_hash_16_xor(bytes: &[u8], limit: Option<usize>) -> u16 {
63    let bytes = fnv1a_hash_32(bytes, limit).to_ne_bytes();
64    let upper: u16 = u16::from_ne_bytes([bytes[0], bytes[1]]);
65    let lower: u16 = u16::from_ne_bytes([bytes[2], bytes[3]]);
66    upper ^ lower
67}
68
69/// Computes 16-bit fnv1a hash from a str using XOR folding.
70#[inline(always)]
71pub const fn fnv1a_hash_str_16_xor(input: &str) -> u16 {
72    fnv1a_hash_16_xor(input.as_bytes(), None)
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    fn test_hash<T: Eq + core::fmt::Debug>(hash_func: impl FnOnce(&str) -> T, source_str: &str, expected: T) {
80        let hashed = hash_func(source_str);
81        let bit_size = core::mem::size_of::<T>() * 8;
82        assert_eq!(hashed, expected, "fnv1a-{bit_size} hash for {source_str}")
83    }
84
85    const FOOBAR: &str = "foobar";
86    const FOOBAR_HASH_32:  u32  = 0xbf9cf968;
87    const FOOBAR_HASH_64:  u64  = 0x85944171f73967e8;
88    const FOOBAR_HASH_128: u128 = 0x343e1662793c64bf6f0d3597ba446f18;
89
90    #[test]
91    fn test_32() {
92        test_hash(fnv1a_hash_str_32, FOOBAR, FOOBAR_HASH_32)
93    }
94    #[test]
95    fn test_64() {
96        test_hash(fnv1a_hash_str_64, FOOBAR, FOOBAR_HASH_64)
97     }
98    #[test]
99    fn test_128() { 
100        test_hash(fnv1a_hash_str_128, FOOBAR, FOOBAR_HASH_128)
101    }
102}