//#region src/index.ts /** * Compute the 64-bit FNV-1a hash of a string as two 32-bit lanes. * * This is the fast core: no BigInt, no allocations, plain `Math.imul`-free * 32-bit arithmetic. Prefer {@link fnv1a64Hex} or {@link fnv1a64Base36} for a * usable key; use this directly only when you want to avoid string formatting. * * The hash is computed over UTF-16 code units (`str.charCodeAt(i)`), not UTF-8 * bytes. For ASCII input this matches a canonical FNV-1a-64; for non-ASCII it * does not. See the README for details. * * @param str - The string to hash. * @returns The `{ high, low }` 32-bit lanes of the 64-bit hash. */ function fnv1a64(str) { const len = str.length; let i = 0; let t0 = 0; let v0 = 8997; let t1 = 0; let v1 = 33826; let t2 = 0; let v2 = 40164; let t3 = 0; let v3 = 52210; while (i < len) { v0 ^= str.charCodeAt(i++); t0 = v0 * 435; t1 = v1 * 435; t2 = v2 * 435; t3 = v3 * 435; t2 += v0 << 8; t3 += v1 << 8; t1 += t0 >>> 16; v0 = t0 & 65535; t2 += t1 >>> 16; v1 = t1 & 65535; v3 = t3 + (t2 >>> 16) & 65535; v2 = t2 & 65535; } return { high: (v3 << 16 | v2) >>> 0, low: (v1 << 16 | v0) >>> 0 }; } /** * Compute the 64-bit FNV-1a hash of a string as a `bigint`. * * Ergonomic and comparable, at the cost of composing the two lanes into a * `bigint`. For a compact string key, prefer {@link fnv1a64Base36}. * * @param str - The string to hash. * @returns The 64-bit hash as an unsigned `bigint`. */ function fnv1a64BigInt(str) { const { high, low } = fnv1a64(str); return BigInt(high) << 32n | BigInt(low); } const hexDigits = "0123456789abcdef"; /** * Every byte value rendered as its two hex digits, so a 32-bit lane formats in * 4 lookups instead of `toString(16)` plus a `padStart`. Leading zeros are * intrinsic to the table, which is what makes the padding free. */ const hexPairs = Array.from({ length: 256 }, (_, i) => hexDigits.charAt(i >> 4) + hexDigits.charAt(i & 15)); /** * Compute the 64-bit FNV-1a hash of a string as a 16-character zero-padded * lowercase hex string. * * The output is always exactly 16 characters, so equal-length comparison and * fixed-width storage are safe. * * @param str - The string to hash. * @returns A 16-character hex string. */ function fnv1a64Hex(str) { const { high, low } = fnv1a64(str); return hexPairs[high >>> 24] + hexPairs[high >>> 16 & 255] + hexPairs[high >>> 8 & 255] + hexPairs[high & 255] + hexPairs[low >>> 24] + hexPairs[low >>> 16 & 255] + hexPairs[low >>> 8 & 255] + hexPairs[low & 255]; } /** * Compute the 64-bit FNV-1a hash of a string as a base36 string. * * This is the shortest textual form (up to 13 characters) and is ideal for * cache keys. The length varies with the value; it is not zero-padded. Equal * inputs always produce identical strings. * * @param str - The string to hash. * @returns A base36 string of the 64-bit hash. */ function fnv1a64Base36(str) { return fnv1a64BigInt(str).toString(36); } //#endregion export { fnv1a64, fnv1a64Base36, fnv1a64BigInt, fnv1a64Hex };