1#[derive(Debug, Clone)]
13pub struct FastHasher {
14 coeffs_a: Vec<u64>,
16 coeffs_b: Vec<u64>,
18 num_hashes: usize,
20}
21
22const MERSENNE_PRIME: u64 = (1_u64 << 61) - 1;
24
25const PHI: u64 = 0x9e37_79b9_7f4a_7c15;
27
28impl FastHasher {
29 pub fn new(num_hashes: usize, seed: u64) -> Self {
37 let num_hashes = num_hashes.min(crate::config::MAX_SIGNATURE_SIZE);
38 if num_hashes == 0 {
39 return Self {
40 coeffs_a: Vec::new(),
41 coeffs_b: Vec::new(),
42 num_hashes: 0,
43 };
44 }
45
46 let mut coeffs_a = Vec::with_capacity(num_hashes);
47 let mut coeffs_b = Vec::with_capacity(num_hashes);
48
49 let mut state = seed.wrapping_add(PHI);
51 for _ in 0..num_hashes {
52 state = splitmix64(state);
53 coeffs_a.push(state | 1);
55 state = splitmix64(state);
56 coeffs_b.push(state);
57 }
58
59 Self {
60 coeffs_a,
61 coeffs_b,
62 num_hashes,
63 }
64 }
65
66 #[allow(dead_code)]
71 pub fn hash_shingle(&self, shingle: u64) -> Vec<u32> {
72 let mut result = Vec::with_capacity(self.num_hashes);
73
74 for i in 0..self.num_hashes {
75 let hash = self.hash_single(shingle, i);
76 result.push(hash);
77 }
78
79 result
80 }
81
82 pub fn update_signature(&self, signature: &mut [u32], shingle: u64) {
86 const CHUNK_SIZE: usize = 8;
87
88 let limit = signature.len().min(self.num_hashes);
91
92 for chunk_start in (0..limit).step_by(CHUNK_SIZE) {
94 let chunk_end = (chunk_start + CHUNK_SIZE).min(limit);
95
96 for i in chunk_start..chunk_end {
97 let hash = self.hash_single(shingle, i);
98 if hash < signature[i] {
99 signature[i] = hash;
100 }
101 }
102 }
103 }
104
105 #[inline]
107 fn hash_single(&self, shingle: u64, idx: usize) -> u32 {
108 let a = self.coeffs_a[idx];
111 let b = self.coeffs_b[idx];
112
113 let product = u128::from(a).wrapping_mul(u128::from(shingle));
114 let sum = product.wrapping_add(u128::from(b));
115
116 let reduced = mod_mersenne(sum);
118
119 reduced as u32
121 }
122
123 #[allow(dead_code)]
127 pub fn update_signature_batch(&self, signature: &mut [u32], shingles: &[u64]) {
128 for shingle in shingles {
129 self.update_signature(signature, *shingle);
130 }
131 }
132
133 #[must_use]
135 pub const fn num_hashes(&self) -> usize {
136 self.num_hashes
137 }
138}
139
140#[inline]
143fn mod_mersenne(mut x: u128) -> u64 {
144 const MASK: u128 = (1_u128 << 61) - 1;
148 let p = u128::from(MERSENNE_PRIME);
149
150 while (x >> 61) != 0 {
152 let low = x & MASK;
153 let high = x >> 61;
154 x = low + high;
155 }
156
157 if x >= p {
159 (x - p) as u64
160 } else {
161 x as u64
162 }
163}
164
165#[inline]
167const fn splitmix64(state: u64) -> u64 {
168 let mut z = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
169 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
170 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
171 z ^ (z >> 31)
172}
173
174#[must_use]
178pub fn hash_bytes(data: &[u8]) -> u64 {
179 const C1: u64 = 0x87c3_7b91_1142_53d5;
180 const C2: u64 = 0x4cf5_ad43_2745_937f;
181 const SEED: u64 = 0x9e37_79b9_7f4a_7c15;
182
183 let mut h = SEED;
184
185 let chunks = data.chunks_exact(8);
187 let remainder = chunks.remainder();
188
189 for chunk in chunks {
190 let mut k = u64::from_le_bytes([
191 chunk[0], chunk[1], chunk[2], chunk[3],
192 chunk[4], chunk[5], chunk[6], chunk[7],
193 ]);
194 k = k.wrapping_mul(C1);
195 k = k.rotate_left(31);
196 k = k.wrapping_mul(C2);
197
198 h ^= k;
199 h = h.rotate_left(27);
200 h = h.wrapping_mul(5).wrapping_add(0x52ce_adbe_e7ef_7e45);
201 }
202
203 if !remainder.is_empty() {
205 let mut k = 0_u64;
206 for (i, &b) in remainder.iter().enumerate() {
207 k ^= u64::from(b) << (i * 8);
208 }
209 k = k.wrapping_mul(C1);
210 k = k.rotate_left(31);
211 k = k.wrapping_mul(C2);
212 h ^= k;
213 }
214
215 h ^= data.len() as u64;
217 h ^= h >> 33;
218 h = h.wrapping_mul(0xff51_afd7_ed55_8ccd);
219 h ^= h >> 33;
220 h = h.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
221 h ^= h >> 33;
222
223 h
224}
225
226#[must_use]
228#[allow(dead_code)]
229pub fn hash_str(s: &str) -> u64 {
230 hash_bytes(s.as_bytes())
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn hasher_creates_correct_size() {
239 let hasher = FastHasher::new(128, 42);
240 assert_eq!(hasher.num_hashes(), 128);
241 assert_eq!(hasher.coeffs_a.len(), 128);
242 assert_eq!(hasher.coeffs_b.len(), 128);
243 }
244
245 #[test]
251 fn new_clamps_hostile_num_hashes() {
252 let hasher = FastHasher::new(usize::MAX, 42);
253 assert_eq!(hasher.num_hashes(), crate::config::MAX_SIGNATURE_SIZE);
254 assert_eq!(hasher.coeffs_a.len(), crate::config::MAX_SIGNATURE_SIZE);
255 }
256
257 #[test]
258 fn hash_single_deterministic() {
259 let hasher = FastHasher::new(64, 12345);
260 let h1 = hasher.hash_single(0xdead_beef, 0);
261 let h2 = hasher.hash_single(0xdead_beef, 0);
262 assert_eq!(h1, h2);
263 }
264
265 #[test]
266 fn different_shingles_different_hashes() {
267 let hasher = FastHasher::new(64, 42);
268 let sig1 = hasher.hash_shingle(1);
269 let sig2 = hasher.hash_shingle(2);
270
271 assert_ne!(sig1, sig2);
273 }
274
275 #[test]
276 fn update_signature_works() {
277 let hasher = FastHasher::new(64, 42);
278 let mut sig = vec![u32::MAX; 64];
279
280 hasher.update_signature(&mut sig, 12345);
281
282 assert!(sig.iter().any(|&x| x < u32::MAX));
284 }
285
286 #[test]
287 fn signature_minimum_property() {
288 let hasher = FastHasher::new(32, 42);
289 let mut sig = vec![u32::MAX; 32];
290
291 hasher.update_signature(&mut sig, 100);
293 let first_sig = sig.clone();
294
295 hasher.update_signature(&mut sig, 200);
297
298 for i in 0..32 {
299 assert!(sig[i] <= first_sig[i]);
300 }
301 }
302
303 #[test]
304 fn hash_bytes_deterministic() {
305 let data = b"hello world";
306 let h1 = hash_bytes(data);
307 let h2 = hash_bytes(data);
308 assert_eq!(h1, h2);
309 }
310
311 #[test]
312 fn hash_bytes_different_input_different_output() {
313 let h1 = hash_bytes(b"hello");
314 let h2 = hash_bytes(b"world");
315 assert_ne!(h1, h2);
316 }
317
318 #[test]
319 fn hash_str_same_as_bytes() {
320 let s = "hello world";
321 assert_eq!(hash_str(s), hash_bytes(s.as_bytes()));
322 }
323
324 #[test]
325 fn splitmix64_produces_varied_output() {
326 let s1 = splitmix64(1);
327 let s2 = splitmix64(2);
328 assert_ne!(s1, s2);
329 }
330
331 #[test]
332 fn mod_mersenne_reduction() {
333 let p = u128::from(MERSENNE_PRIME);
335
336 assert_eq!(mod_mersenne(12345), 12345);
338
339 assert_eq!(mod_mersenne(p), 0);
341
342 assert_eq!(mod_mersenne(p + 1), 1);
344 }
345
346 #[test]
347 fn batch_update_matches_individual() {
348 let hasher = FastHasher::new(32, 42);
349 let shingles = vec![1_u64, 2, 3, 4, 5];
350
351 let mut sig_batch = vec![u32::MAX; 32];
352 hasher.update_signature_batch(&mut sig_batch, &shingles);
353
354 let mut sig_individual = vec![u32::MAX; 32];
355 for shingle in &shingles {
356 hasher.update_signature(&mut sig_individual, *shingle);
357 }
358
359 assert_eq!(sig_batch, sig_individual);
360 }
361
362 #[test]
363 fn hash_distribution_uniform() {
364 let hasher = FastHasher::new(64, 42);
366 let mut bins = [0_u32; 16];
367
368 for i in 0..10000 {
369 let hash = hasher.hash_single(i, 0);
370 let bin = (hash >> 28) as usize % 16; bins[bin] += 1;
372 }
373
374 let expected = 10000 / 16;
377 for count in &bins {
378 assert!(
379 *count >= expected / 2 && *count <= expected * 3 / 2,
380 "bin count {} is outside expected range around {}",
381 count,
382 expected
383 );
384 }
385 }
386}