DSA DSA In Depth
0%
lesson hashinguniversal-hashingfibonacci-hashing

Hash Functions: Division, Multiplicative, Universal & k-Independent Families

7 min read Module 2.1: Hash Tables, Modern Probing & Probabilistic Sketches Updated Oct 23, 2026
📄 Cheat Sheet
MIT 6.006 Lecture 8: Hashing with Chaining — Division, Multiplication & Universal Hashing
MIT OpenCourseWare (Erik Demaine) MIT 6.006 Lecture 8: Hashing with Chaining — Division, Multiplication & Universal Hashing Watch on YouTube ↗

Erik Demaine exposes why deterministic hash functions are vulnerable to adversarial O(N) collisions and proves why Carter-Wegman universal hash families [(ax + b) mod p] mod m guarantee O(1) expected lookups.

0:00 / --:--

Deterministic Hashing vs Carter-Wegman Universal Families

Classical textbooks begin with the Simple Uniform Hashing Assumption (SUHA), which posits that keys are drawn independently and uniformly from the universe $\mathcal{U}$. In production systems, keys are never uniform: memory addresses are aligned multiples of $8$ or $16$, IPv4 subnets cluster in high bits, and sequential database IDs exhibit arithmetic progressions.

  • Division Method ($h(k) = k \bmod m$): If $m = 2^r$, $k \bmod 2^r$ simply extracts the lowest $r$ bits of $k$, ignoring the upper $w - r$ bits completely. For pointer keys aligned to 16-byte heap boundaries, the lowest 4 bits are always $0000_2$, leaving $15/16$ of buckets permanently empty. Choosing a prime $m$ not close to a power of $2$ mixes bits better, but 64-bit integer division (DIV r64) costs 20–40 cycles on modern x86-64 cores.
  • Fibonacci Multiplicative & Multiply-Shift Hashing: Given word size $w = 64$ and table size $m = 2^r$, Knuth's multiplicative method multiplies $k$ by an odd constant $A \approx \phi^{-1} \cdot 2^{64} = \text{0x9E3779B97F4A7C15}$ (derived from the golden ratio $\phi = (1+\sqrt{5})/2$) and extracts the top $r$ bits: $h(k) = (k \cdot A) \gg (64 - r)$. Dietzfelbinger et al. proved that selecting a random odd 64-bit multiplier $a$ at startup makes $h_a(k) = (a \cdot k) \gg (64 - r)$ a 2-approximate universal family with collision probability $\le 2/m$.
  • Carter-Wegman Universal Family ($\mathcal{H}_{p,m}$): Choose a prime $p > |\mathcal{U}|$ (e.g., Mersenne prime $p = 2^{61} - 1$), and draw random $a \in \{1, \dots, p-1\}$, $b \in \{0, \dots, p-1\}$. Then $h_{a,b}(k) = ((ak + b) \bmod p) \bmod m$ satisfies strict universality: $\Pr_{h \in \mathcal{H}}[h(x) = h(y)] \le 1/m$ for all $x \ne y$.
Hash Family Formula ($m = 2^r$ or prime) Independence Collision Bound ($x \ne y$) CPU Latency (x86-64)
Division Method $k \bmod m$ Deterministic (0) Adversarial $1$ 20–40 cycles (DIV)
Multiply-Shift (Dietzfelbinger) $(a \cdot k) \gg (w - r)$, odd $a$ Near 2-Universal $\le 2 / m$ 3–4 cycles (IMUL + SHR)
Multiply-Add-Shift (Strong) $((a \cdot k + b) \bmod 2^{2w}) \gg (2w - r)$ Pairwise (2-Indep.) $= 1 / m$ 5–6 cycles (128-bit product)
Carter-Wegman Polynomial $\left(\sum_{i=0}^{k-1} a_i x^i \bmod p\right) \bmod m$ $k$-Wise Independent $= 1 / m^k$ ($k$-tuple) $O(k)$ Mul-Add + Mersenne fold

Formal Proof: Expected Chain Length Under Universal Hashing

Definition (Universal Family): A family $\mathcal{H}$ of hash functions mapping $\mathcal{U} \to \{0, \dots, m-1\}$ is universal if for every distinct pair $x, y \in \mathcal{U}$ ($x \ne y$):

$$\Pr_{h \sim \mathcal{H}}[h(x) = h(y)] \le \frac{1}{m}$$

Theorem: For any fixed set $S \subseteq \mathcal{U}$ of $n$ keys and any query key $q$, let $C_{q,x} = \mathbb{I}[h(q) = h(x)]$ be the indicator random variable for a collision between $q$ and $x \in S \setminus \{q\}$. By linearity of expectation (which requires no higher-order independence beyond pairwise):

$$\mathbb{E}_{h}\left[\sum_{x \in S \setminus \{q\}} C_{q,x}\right] = \sum_{x \in S \setminus \{q\}} \Pr[h(q) = h(x)] \le \sum_{x \in S \setminus \{q\}} \frac{1}{m} \le \frac{n}{m} = \alpha$$

Thus expected lookup cost is bounded by $1 + \alpha = O(1)$ even if an adversary selects $S$ and $q$, provided the adversary does not know the random seed $(a, b)$ chosen from $\mathcal{H}$.

Visualizing Multiply-Shift & Carter-Wegman Bit Dispersion

The SVG diagram below contrasts naive modulo masking against 64-bit Multiply-Shift hashing, where low-entropy key bits are diffused upward into the top $r$ index bits via multiplication by an odd 64-bit random seed.

64-bit Key k (Aligned Ptr) 0x00007F9A_004210_00 (low 4b=0) × Odd Seed a (64-bit) 0x9E3779B97F4A7C15 | 1 Top r Bits: (a·k) >> (64-r) Bucket ∈ [0, 2^r - 1] k-Independent Polynomial Family over Mersenne Prime p = 2^61 - 1: h(x) = ( (a_{k-1}·x^{k-1} + ... + a_1·x + a_0) mod (2^61 - 1) ) mod m • k=2 (Pairwise): Chaining, Count-Min Sketch • k=5: Linear Probing O(1) • k=O(log n): Cuckoo Hashing
flowchart LR K["Input Key x ∈ U"] --> P{"Key Width?"} P -- "64-bit Integer" --> MS["Multiply-Add-Shift ((a·x + b) >> (64 - r))"] P -- "Variable String / Bytes" --> POLY["Polynomial Rolling / WyHash Fold 64-bit blocks mod 2^61-1"] MS --> F["Final Mixing (Murmur3 / SplitMix64 Avalanche)"] POLY --> F F --> B["Bucket Index h(x) ∈ [0, m-1]"]
Universal Hashing, Birthday Collision Curve & HashDoS vs Keyed SipHash
Pr[h(x)=h(y)] ≤ 1/M · SipHash-2-4

Witness how an unkeyed polynomial hash (h = 31·h + c) degenerates to O(N²) under an adversarial HashDoS collision payload, how keyed SipHash-2-4 ARX rounds & Carter-Wegman universal families enforce Pr[collision] ≤ 1/M, and how the Birthday Paradox curve 1 − e^(−k(k−1)/2M) crosses 50% at k ≈ 1.177√M.

Step 1 / 10
Custom Input:
Timeline

Invariant & State

Unkeyed Java/DJB2 Hash: h("Aa") = 31·65 + 97 = 2112 ≡ 3 (mod 8). Placed at Bucket[3].
Bucket[3]Degenerate Collision Chain (h = 2112 mod 8 = 3): "Aa" Cumulative Comparisons = 1 + 2 + … + 1 = 1 = N(N+1)/2 = Θ(N²)
[0]·
[1]·
[2]·
[3]Chain(1)len=1 (Hotspot!)
[4]·
[5]·
[6]·
[7]·
Payload Key
"Aa"
Unkeyed Hash
2112 → Bucket[3]
Bucket[3] Length
1 / 1 keys
Total Comparisons
1 (Θ(N²))
[Step 1] Insert #1 ("Aa"): unkeyed hash h("Aa") = 2112 maps to Bucket[3] (1 probe).

Hardware Reality: Fast Mersenne Modulo ($2^{61}-1$) & Lemire's FastRange

Executing x % p via hardware DIV takes 20–40 cycles and blocks the execution port. However, when $p = 2^{61} - 1$ is a Mersenne prime, $2^{61} \equiv 1 \pmod p$. Any 128-bit product $z = a \cdot x$ can be reduced modulo $2^{61}-1$ using zero divisions: split $z$ into its low 61 bits ($z \mathbin{\&} p$) and upper bits ($z \gg 61$), add them together, and subtract $p$ if the sum $\ge p$. Similarly, mapping a 32-bit hash $h$ into an arbitrary non-power-of-two bucket count $m$ without DIV uses Lemire's FastRange: $\lfloor (h \cdot m) / 2^{32} \rfloor = (\text{uint64\_t}(h) \cdot m) \gg 32$.

Production C++20 Implementation: Pairwise-Independent & Mersenne $k$-Independent Hashers

// C++20: Dietzfelbinger Multiply-Add-Shift (2-Independent) & Carter-Wegman mod (2^61 - 1)
#include <cstdint>
#include <array>

// 1. Strongly 2-Universal (Pairwise Independent) Multiply-Add-Shift from 64-bit to r bits
struct MultiplyAddShiftHash {
    __uint128_t a; // Random odd 128-bit multiplier
    __uint128_t b; // Random 128-bit additive offset
    uint32_t r;    // log2(table_size)

    uint64_t operator()(uint64_t key) const noexcept {
        // Full 128-bit arithmetic modulo 2^128, then extract top r bits of low 64-bit half
        uint64_t mixed = static_cast<uint64_t>((a * key + b) >> 64);
        return mixed >> (64 - r);
    }
};

// 2. Branchless Mersenne Prime (p = 2^61 - 1) k-Independent Polynomial Hash
template <size_t K>
class MersenneKIndependentHash {
    static constexpr uint64_t P = (1ULL << 61) - 1;
    std::array<uint64_t, K> coeff_; // Random seeds in [0, P - 1]

    static uint64_t modMersenne(__uint128_t z) noexcept {
        uint64_t res = static_cast<uint64_t>(z & P) + static_cast<uint64_t>(z >> 61);
        return (res >= P) ? (res - P) : res;
    }

public:
    explicit constexpr MersenneKIndependentHash(std::array<uint64_t, K> seeds) noexcept
        : coeff_(seeds) {}

    uint64_t evaluate(uint64_t x) const noexcept {
        x = modMersenne(x);
        uint64_t acc = coeff_[K - 1];
        for (size_t i = K - 1; i-- > 0; ) {
            acc = modMersenne(static_cast<__uint128_t>(acc) * x + coeff_[i]);
        }
        return acc; // Uniformly distributed in [0, 2^61 - 2]
    }

    // Lemire's FastRange reduction to [0, m - 1] without integer division
    uint32_t bucket(uint64_t x, uint32_t m) const noexcept {
        uint64_t h32 = evaluate(x) & 0xFFFFFFFFULL;
        return static_cast<uint32_t>((h32 * static_cast<uint64_t>(m)) >> 32);
    }
};

Failure Modes & Adversarial Pitfalls

  • C++ std::hash<uint64_t> Identity Function Trap: In libstdc++ (GCC) and libc++ (Clang), std::hash<uint64_t> is implemented as the identity function return x;! Passing pointer keys or multiples of $2^k$ into a power-of-two table without a finalizer (like SplitMix64 or Fibonacci multiplication) causes catastrophic $O(N)$ bucket collisions.
  • Insufficient Independence for Linear Probing: While 2-independence (pairwise independence) suffices for Separate Chaining and Count-Min Sketch, Pagh et al. proved that 2-independent families can suffer $\Omega(\log N)$ expected probe lengths in Linear Probing; 5-independence (or Practical Tabulation Hashing) is required to guarantee $O(1)$ expected probes in Linear Probing.
Check yourself Question 1 of 4
Concept Check Formal Definition of a Carter-Wegman Universal Hash Family recall

A family H of hash functions mapping universe U to {0, 1, ..., m - 1} is defined as 'universal' (Carter & Wegman, 1979) if and only if for every pair of distinct keys x != y in U:

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

While 2-independence (pairwise independence) suffices to prove O(1) expected operation time in Separate Chaining and Count-Min Sketch, what did Pagh, Pagh, and Ruzic (2007) prove regarding k-independence for Linear Probing?

Key Takeaways

Deterministic hash functions always admit worst-case O(N) collision sets when |U| > N·m; Universal Hashing randomizes the function choice h ∈ H at runtime so every input achieves O(1 + α) expected lookups.
Linearity of expectation requires only pairwise collision bounds Pr[h(x) = h(y)] ≤ 1/m to prove that expected chain length is at most 1 + α.
Dietzfelbinger's Multiply-Shift hashing (a·k >> (64-r) with random odd a) achieves 2/m collision bounds in 3 CPU cycles without slow hardware DIV instructions.
Mersenne primes (p = 2^61 - 1) allow k-wise independent polynomial evaluation using bitwise shifts and additions instead of 128-bit division.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue