/*! \brief MurmurHash3 JavaScript implementation. All functions return Uint32 to fit max array length and indicies (\see https://stackoverflow.com/a/6155063/2895579). Seeds implied to be Uint32 too. \see https://sites.google.com/site/murmurhash/ \see https://sites.google.com/site/murmurhash/avalanche \see https://github.com/aappleby/smhasher Benchmarks: \see https://github.com/evg656e/vbase-benchmarks/blob/master/benchmarks/hash/results.md */ import { slice } from '../../core/array'; import { convertFloat64ToUint32, fuzzyConvertFloat64ToUint32, isMinusZero } from '../../math/float'; function rotl32(x: number, r: number) { return (x << r) | (x >>> (32 - r)); } function fmix32(x: number) { x ^= x >>> 16; x = Math.imul(x, 0x85ebca6b); x ^= x >>> 13; x = Math.imul(x, 0xc2b2ae35); x ^= x >>> 16; return x; } const c1 = 0xcc9e2d51; const c2 = 0x1b873593; export function genericMurmurHash(getblock32: (key: T, i: number) => number, gettail: (key: T, i: number) => number[]) { return function (key: T, len: number, seed: number, virtualStep: number) { const nblocks = (len / 4) >>> 0; let h1 = seed; let virtualIndex = 0; for (let i = 0; i < nblocks; i++ , virtualIndex += virtualStep) { let k1 = getblock32(key, virtualIndex); k1 = Math.imul(k1, c1); k1 = rotl32(k1, 15); k1 = Math.imul(k1, c2); h1 ^= k1; h1 = rotl32(h1, 13); h1 = (Math.imul(h1, 5) + 0xe6546b64) | 0; } const rem = len & 3; if (rem !== 0) { const tail = gettail(key, virtualIndex); let k1 = 0; switch (rem) { case 3: k1 ^= tail[2] << 16; case 2: k1 ^= tail[1] << 8; case 1: k1 ^= tail[0]; k1 = Math.imul(k1, c1); k1 = rotl32(k1, 15); k1 = Math.imul(k1, c2); h1 ^= k1; } } h1 ^= len; h1 = fmix32(h1); return h1 >>> 0; }; } const hashInt32Impl = genericMurmurHash((key: number) => key, () => []); export function hashInt32(key: number, seed = 0) { return hashInt32Impl(key, 4, seed, 4); } export { hashInt32 as hashUint32 }; export function hashBoolean(key: boolean, seed?: number) { return hashInt32(Number(key), seed); } const hashStringImpl = genericMurmurHash((key: string, i: number) => { return key.charCodeAt(i) | key.charCodeAt(i + 1) << 16; }, (key: string, i: number) => { const code = key.charCodeAt(i); return [code & 0xff, ((code >>> 8) & 0xff) << 8]; }); export function hashString(key: string, seed = 0) { return hashStringImpl(key, key.length * 2, seed, 2); } const hashAsciiStringImpl = genericMurmurHash( (key: string, i: number) => (key.charCodeAt(i) & 0xff) | ((key.charCodeAt(i + 1) & 0xff) << 8) | ((key.charCodeAt(i + 2) & 0xff) << 16) | ((key.charCodeAt(i + 3) & 0xff) << 24), (key: string, i: number) => slice(key, i).map((key: string) => key.charCodeAt(0) & 0xff) ); export function hashAsciiString(key: string, seed = 0) { return hashAsciiStringImpl(key, key.length, seed, 4); } const hashUint8ArrayImpl = genericMurmurHash( (key: Uint8Array, i: number) => key[i] | (key[i + 1] << 8) | (key[i + 2] << 16) | (key[i + 3] << 24), (key: Uint8Array, i: number) => slice(key, i) ); export function hashUint8Array(key: Uint8Array, seed = 0) { return hashUint8ArrayImpl(key, key.length, seed, 4); } const hashUint32ArrayImpl = genericMurmurHash((key: number[], i: number) => key[i], () => []); export function hashUint32Array(key: number[], seed = 0) { return hashUint32ArrayImpl(key, key.length * 4, seed, 1); } export function hashUint32Pair(key: [number, number], seed = 0) { return hashUint32ArrayImpl(key, 8, seed, 1); } export function hashFloat64(key: number, seed = 0) { // slow, precise return hashUint32ArrayImpl(convertFloat64ToUint32(key, true), 8, seed, 1); } export function fuzzyHashFloat64(key: number, seed = 0) { // fast, fuzzy return hashUint32ArrayImpl(fuzzyConvertFloat64ToUint32(key, true), 8, seed, 1); } export function hashNumber(key: number, seed?: number) { // slow, precise if ((((key | 0) === key) // isInt32(key) || ((key >>> 0) === key)) // isUint32(key) && !isMinusZero(key)) return hashInt32(key, seed); return hashFloat64(key, seed); } export function fuzzyHashNumber(key: number, seed?: number) { // fast, fuzzy if ((((key | 0) === key) // isInt32(key) || ((key >>> 0) === key)) // isUint32(key) && !isMinusZero(key)) return hashInt32(key, seed); return fuzzyHashFloat64(key, seed); }