A Bloom filter needs several independent hashes, not one. Rather than write a whole second hash function, we scramble the first hash's output through the splitmix64 mixing steps to get a well-spread second value.
Derive a second hash by passing Hash1's output through the splitmix64 finalizer.
The next lesson needs two hashes that behave independently, so that combining them produces well-spread bit positions. Computing a whole second hash over the input bytes would work, but there is a cheaper trick: take the first hash’s output and run it through a strong bit mixer. The splitmix64 finalizer is a fixed sequence of add, multiply, and xor-shift steps that thoroughly scrambles a 64-bit value, so mix(Hash1(x)) is effectively independent of Hash1(x) even though it is derived from it.
Each x >> n is a right shift that folds high bits down into the low bits, and each multiply by a large odd constant diffuses them back across the whole word; two rounds are enough to erase any structure. We now have Hash1 and Hash2 for any input, computed from a single pass over the bytes - the two ingredients double hashing needs next.
func mix(x uint64) uint64 {x += 0x9E3779B97F4A7C15x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9x = (x ^ (x >> 27)) * 0x94D049BB133111EBreturn x ^ (x >> 31)}func Hash2(data []byte) uint64 { return mix(Hash1(data)) }