use crate::city64::{K0, K1, hash_len_16_u64, read_le64, weak_hash_len_32_with_seeds};
use crate::{city_hash256_crc, city_murmur_with_seed};
#[derive(Copy, Clone, Default)]
struct DeinterleavedU128 {
lo: u64,
hi: u64,
}
#[inline]
fn city_128_with_seed_impl(bytes: &[u8], seed: u128) -> u128 {
if bytes.len() < 128 {
return city_murmur_with_seed(bytes, seed);
}
let mut len = bytes.len() as u64;
let mut x = (seed & 0xffff_ffff_ffff_ffff) as u64;
let mut y = (seed >> 64) as u64;
let mut z = len.wrapping_mul(K1);
let mut v: DeinterleavedU128 = DeinterleavedU128::default();
let mut w: DeinterleavedU128 = DeinterleavedU128::default();
v.lo = (y ^ K1)
.rotate_right(49)
.wrapping_mul(K1)
.wrapping_add(read_le64(bytes, 0));
v.hi =
v.lo.rotate_right(42)
.wrapping_mul(K1)
.wrapping_add(read_le64(bytes, 8));
w.lo = y
.wrapping_add(z)
.rotate_right(35)
.wrapping_mul(K1)
.wrapping_add(x);
w.hi = x
.wrapping_add(read_le64(bytes, 88))
.rotate_right(53)
.wrapping_mul(K1);
let mut moved_offset = 0usize;
loop {
let s = &bytes[moved_offset..moved_offset + 64];
x = x
.wrapping_add(y)
.wrapping_add(v.lo)
.wrapping_add(read_le64(s, 8))
.rotate_right(37)
.wrapping_mul(K1);
y = y
.wrapping_add(v.hi.wrapping_add(read_le64(s, 48)))
.rotate_right(42)
.wrapping_mul(K1);
x ^= w.hi;
y = y.wrapping_add(v.lo.wrapping_add(read_le64(s, 40)));
z = z.wrapping_add(w.lo).rotate_right(33).wrapping_mul(K1);
let q0 = weak_hash_len_32_with_seeds(s, 0, v.hi.wrapping_mul(K1), x.wrapping_add(w.lo));
v = DeinterleavedU128 { lo: q0.0, hi: q0.1 };
let q1 = weak_hash_len_32_with_seeds(
s,
32,
z.wrapping_add(w.hi),
y.wrapping_add(read_le64(s, 16)),
);
w = DeinterleavedU128 { lo: q1.0, hi: q1.1 };
std::mem::swap(&mut z, &mut x);
moved_offset += 64;
let s = &bytes[moved_offset..moved_offset + 64];
x = x
.wrapping_add(y)
.wrapping_add(v.lo)
.wrapping_add(read_le64(s, 8))
.rotate_right(37)
.wrapping_mul(K1);
y = y
.wrapping_add(v.hi.wrapping_add(read_le64(s, 48)))
.rotate_right(42)
.wrapping_mul(K1);
x ^= w.hi;
y = y.wrapping_add(v.lo.wrapping_add(read_le64(s, 40)));
z = z.wrapping_add(w.lo).rotate_right(33).wrapping_mul(K1);
let q0 = weak_hash_len_32_with_seeds(s, 0, v.hi.wrapping_mul(K1), x.wrapping_add(w.lo));
v = DeinterleavedU128 { lo: q0.0, hi: q0.1 };
let q1 = weak_hash_len_32_with_seeds(
s,
32,
z.wrapping_add(w.hi),
y.wrapping_add(read_le64(s, 16)),
);
w = DeinterleavedU128 { lo: q1.0, hi: q1.1 };
std::mem::swap(&mut z, &mut x);
moved_offset += 64;
len -= 128;
if len < 128 {
break;
}
}
x = x.wrapping_add(v.lo.wrapping_add(z).rotate_right(49).wrapping_mul(K0));
y = y.wrapping_mul(K0).wrapping_add(w.hi.rotate_right(37));
z = z.wrapping_mul(K0).wrapping_add(w.lo.rotate_right(27));
w.lo = w.lo.wrapping_mul(9);
v.lo = v.lo.wrapping_mul(K0);
let mut tail_done = 0;
while tail_done < len {
tail_done += 32;
y = x
.wrapping_add(y)
.rotate_right(42)
.wrapping_mul(K0)
.wrapping_add(v.hi);
w.lo = w.lo.wrapping_add(read_le64(
bytes,
moved_offset + len as usize - tail_done as usize + 16,
));
x = x.wrapping_mul(K0).wrapping_add(w.lo);
z = z.wrapping_add(w.hi.wrapping_add(read_le64(
bytes,
moved_offset + len as usize - tail_done as usize,
)));
w.hi = w.hi.wrapping_add(v.lo);
let q0 = weak_hash_len_32_with_seeds(
bytes,
moved_offset + len as usize - tail_done as usize,
v.lo.wrapping_add(z),
v.hi,
);
v = DeinterleavedU128 { lo: q0.0, hi: q0.1 };
v.lo = v.lo.wrapping_mul(K0);
}
x = hash_len_16_u64(x, v.lo);
y = hash_len_16_u64(y.wrapping_add(z), w.lo);
let z0 = hash_len_16_u64(x.wrapping_add(v.hi), w.hi).wrapping_add(y);
let z1 = hash_len_16_u64(x.wrapping_add(w.hi), y.wrapping_add(v.hi));
(z0 as u128) | (z1 as u128).wrapping_shl(64)
}
pub fn city_hash128_with_seed(bytes: &[u8], seed: u128) -> u128 {
city_128_with_seed_impl(bytes, seed)
}
pub fn city_hash128(bytes: &[u8]) -> u128 {
if bytes.len() >= 16 {
let q0 = read_le64(bytes, 0);
let q1 = read_le64(bytes, 8).wrapping_add(K0);
city_128_with_seed_impl(&bytes[16..], (q0 as u128) | (q1 as u128).wrapping_shl(64))
} else {
city_128_with_seed_impl(bytes, K0 as u128 | ((K1 as u128).wrapping_shl(64)))
}
}
pub fn city_hash128_crc(bytes: &[u8]) -> u128 {
if bytes.len() <= 900 {
city_hash128(bytes)
} else {
let hash = city_hash256_crc(bytes);
hash.hi
}
}
pub fn city_hash128_crc_with_seed(bytes: &[u8], seed: u128) -> u128 {
if bytes.len() <= 900 {
city_hash128_with_seed(bytes, seed)
} else {
let hash = city_hash256_crc(bytes);
let result_lo = (hash.lo & 0xffff_ffff_ffff_ffff) as u64;
let result_hi = (hash.lo >> 64) as u64;
let result1_lo = (hash.hi & 0xffff_ffff_ffff_ffff) as u64;
let result1_hi = (hash.hi >> 64) as u64;
let u = ((seed >> 64) as u64).wrapping_add(result_lo);
let v = ((seed & 0xffff_ffff_ffff_ffff) as u64).wrapping_add(result_hi);
let l0 = hash_len_16_u64(u, v.wrapping_add(result1_lo));
let l1 = hash_len_16_u64(
v.rotate_right(32),
u.wrapping_mul(K0).wrapping_add(result1_hi),
);
(l0 as u128) | (l1 as u128).wrapping_shl(64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_city128() {
let data2 = b"The original MurmurHash was created as an attempt to make a faster function than Lookup3.[7] Although successful, it had not been tested thoroughly and was not capable of providing 64-bit hashes as in Lookup3. Its design would be later built upon in MurmurHash2, combining a multiplicative hash (similar to the FowlerNollVo hash function) with an Xorshift.";
let hash2 = city_128_with_seed_impl(data2, 0);
assert_eq!(hash2, 181738720256903589065179743458014556635);
}
#[test]
fn test_hash_city128_2() {
let data2 = b"The current version, completed April 3, 2011, is MurmurHash3,[12][13] which yields a 32-bit or 128-bit hash value. When using 128-bits, the x86 and x64 versions do not produce the same values, as the algorithms are optimized for their respective platforms. MurmurHash3 was released alongside SMHasher, a hash function test suite.";
let hash2 = city_128_with_seed_impl(data2, 0);
assert_eq!(hash2, 141227953010020849533055099834667401374);
}
#[test]
fn test_hash_city128_2_with_seed() {
let data2 = b"The current version, completed April 3, 2011, is MurmurHash3,[12][13] which yields a 32-bit or 128-bit hash value. When using 128-bits, the x86 and x64 versions do not produce the same values, as the algorithms are optimized for their respective platforms. MurmurHash3 was released alongside SMHasher, a hash function test suite.";
let hash2 = city_128_with_seed_impl(data2, 125);
assert_eq!(hash2, 119575411414761893753960023769141284138);
}
}