lib0
Version:
isomorphic utility functions
83 lines (77 loc) • 2.65 kB
JavaScript
/**
* @module fnv1a
* FNV-1a (32bit) - a fast, non-cryptographic hash function.
* Spec: http://www.isthe.com/chongo/tech/comp/fnv/
*/
import * as math from '../math.js'
/**
* FNV-1a offset basis (32bit).
*/
export const offsetBasis = 0x811c9dc5
/**
* FNV-1a prime (32bit).
*/
export const prime = 0x01000193
/**
* Compute the FNV-1a 32bit hash of a byte sequence.
*
* Pass the result of a previous call as `hash` to digest a message in chunks:
* `digest(concat(a, b)) === digest(b, digest(a))`.
*
* @param {Uint8Array} data
* @param {number} hash - continue hashing from a previous result (defaults to the offset basis)
* @return {number} unsigned 32bit hash
*/
/* @__NO_SIDE_EFFECTS__ */
export const digest = (data, hash = offsetBasis) => {
for (let i = 0; i < data.length; i++) {
hash = math.imul(hash ^ data[i], prime)
}
return hash >>> 0
}
/**
* Compute the FNV-1a 32bit hash of a string - without allocating the utf8 encoding.
*
* Equivalent to `digest(string.encodeUtf8(str))`: the string is hashed as utf8 bytes; lone
* surrogates are hashed as the replacement character (like TextEncoder encodes them).
*
* Prefer this over `digest(string.encodeUtf8(str))` when hashing strings - it skips the
* TextEncoder call and the Uint8Array allocation, making it ~5x faster for small strings
* (<=20 chars, e.g. object keys) and ~2x faster for larger ones.
*
* @param {string} str
* @param {number} hash - continue hashing from a previous result (defaults to the offset basis)
* @return {number} unsigned 32bit hash
*/
/* @__NO_SIDE_EFFECTS__ */
export const digestString = (str, hash = offsetBasis) => {
for (let i = 0; i < str.length; i++) {
let c = str.charCodeAt(i)
if (c < 0x80) {
hash = math.imul(hash ^ c, prime)
} else {
if (c >= 0xd800 && c < 0xe000) {
const lo = str.charCodeAt(i + 1) // NaN when out of bounds ⇒ NaN & x === 0
if (c < 0xdc00 && (lo & 0xfc00) === 0xdc00) {
c = 0x10000 + ((c & 0x3ff) << 10) + (lo & 0x3ff)
i++
} else {
c = 0xfffd // lone surrogate ⇒ replacement character
}
}
if (c < 0x800) {
hash = math.imul(hash ^ (0xc0 | (c >> 6)), prime)
} else {
if (c < 0x10000) {
hash = math.imul(hash ^ (0xe0 | (c >> 12)), prime)
} else {
hash = math.imul(hash ^ (0xf0 | (c >> 18)), prime)
hash = math.imul(hash ^ (0x80 | ((c >> 12) & 0x3f)), prime)
}
hash = math.imul(hash ^ (0x80 | ((c >> 6) & 0x3f)), prime)
}
hash = math.imul(hash ^ (0x80 | (c & 0x3f)), prime)
}
}
return hash >>> 0
}