Go MurmurHash2 Code Example (Online Runner)

Go MurmurHash2 examples with variant selection, seeds, and number/hex output matching the MurmurHash2 tools.

Online calculator: use the site MurmurHash2 text tool.

Calculation method

MurmurHash2 supports multiple variants (32-bit x86, 64-bit x64/x86, and the Merkle-Damgard flavor). This helper mirrors those variants and output formats.

Implementation notes

  • Package: no external dependencies; this is a pure Go implementation.
  • Implementation: seeds are parsed as 32-bit values (decimal or hex) to match the tool.
  • Notes: MurmurHash2 is not cryptographic; use it only for non-security checksums.

Text hashing example

go
package main

import (
	"encoding/binary"
	"fmt"
)

func murmur2X86_32(data []byte, seed uint32) uint32 {
	const m uint32 = 0x5BD1E995
	const r uint32 = 24
	length := len(data)
	h := seed ^ uint32(length)
	i := 0

	for length >= 4 {
		k := binary.LittleEndian.Uint32(data[i:])
		k *= m
		k ^= k >> r
		k *= m
		h *= m
		h ^= k
		i += 4
		length -= 4
	}

	switch length {
	case 3:
		h ^= uint32(data[i+2]) << 16
		fallthrough
	case 2:
		h ^= uint32(data[i+1]) << 8
		fallthrough
	case 1:
		h ^= uint32(data[i])
		h *= m
	}

	h ^= h >> 13
	h *= m
	h ^= h >> 15
	return h
}

func main() {
	fmt.Printf("%08x\n", murmur2X86_32([]byte("hello"), 0))
}

File hashing example

go
package main

import (
	"encoding/binary"
	"fmt"
	"os"
)

func murmur2X86_32(data []byte, seed uint32) uint32 {
	const m uint32 = 0x5BD1E995
	const r uint32 = 24
	length := len(data)
	h := seed ^ uint32(length)
	i := 0

	for length >= 4 {
		k := binary.LittleEndian.Uint32(data[i:])
		k *= m
		k ^= k >> r
		k *= m
		h *= m
		h ^= k
		i += 4
		length -= 4
	}

	switch length {
	case 3:
		h ^= uint32(data[i+2]) << 16
		fallthrough
	case 2:
		h ^= uint32(data[i+1]) << 8
		fallthrough
	case 1:
		h ^= uint32(data[i])
		h *= m
	}

	h ^= h >> 13
	h *= m
	h ^= h >> 15
	return h
}

func main() {
	file, err := os.CreateTemp("", "murmur2-example-*.bin")
	if err != nil {
		panic(err)
	}
	defer os.Remove(file.Name())
	file.WriteString("hello")
	file.Close()

	data, err := os.ReadFile(file.Name())
	if err != nil {
		panic(err)
	}
	fmt.Printf("%08x\n", murmur2X86_32(data, 0))
}

Complete script (implementation + tests)

go
package main

import (
	"encoding/binary"
	"fmt"
	"strconv"
	"strings"
)

type Variant string

type OutputFormat string

const (
	VarX86_32    Variant = "x86_32"
	VarX86_32A   Variant = "x86_32a"
	VarNeutral32 Variant = "neutral_32"
	VarAligned32 Variant = "aligned_32"
	VarX64_64A   Variant = "x64_64a"
	VarX86_64B   Variant = "x86_64b"
)

const (
	OutputNumber OutputFormat = "number"
	OutputHex    OutputFormat = "hex"
)

func parseSeed(value string) uint32 {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return 0
	}
	parsed, err := strconv.ParseUint(trimmed, 0, 32)
	if err != nil {
		return 0
	}
	return uint32(parsed)
}

func murmur2X86_32(data []byte, seed uint32) uint32 {
	const m uint32 = 0x5BD1E995
	const r uint32 = 24
	length := len(data)
	h := seed ^ uint32(length)
	i := 0

	for length >= 4 {
		k := binary.LittleEndian.Uint32(data[i:])
		k *= m
		k ^= k >> r
		k *= m
		h *= m
		h ^= k
		i += 4
		length -= 4
	}

	switch length {
	case 3:
		h ^= uint32(data[i+2]) << 16
		fallthrough
	case 2:
		h ^= uint32(data[i+1]) << 8
		fallthrough
	case 1:
		h ^= uint32(data[i])
		h *= m
	}

	h ^= h >> 13
	h *= m
	h ^= h >> 15
	return h
}

func mmix(h, k uint32) uint32 {
	const m uint32 = 0x5BD1E995
	const r uint32 = 24
	k *= m
	k ^= k >> r
	k *= m
	h *= m
	h ^= k
	return h
}

func murmur2A(data []byte, seed uint32) uint32 {
	const m uint32 = 0x5BD1E995
	const r uint32 = 24
	length := len(data)
	h := seed
	i := 0
	remaining := length

	for remaining >= 4 {
		k := binary.LittleEndian.Uint32(data[i:])
		h = mmix(h, k)
		i += 4
		remaining -= 4
	}

	var t uint32
	switch remaining {
	case 3:
		t ^= uint32(data[i+2]) << 16
		fallthrough
	case 2:
		t ^= uint32(data[i+1]) << 8
		fallthrough
	case 1:
		t ^= uint32(data[i])
	}

	h = mmix(h, t)
	h = mmix(h, uint32(length))

	h ^= h >> 13
	h *= m
	h ^= h >> 15
	return h
}

func murmur64A(data []byte, seed uint32) uint64 {
	const m uint64 = 0xC6A4A7935BD1E995
	const r uint = 47
	h := uint64(seed) ^ (uint64(len(data)) * m)
	i := 0

	for i+8 <= len(data) {
		k := binary.LittleEndian.Uint64(data[i:])
		k *= m
		k ^= k >> r
		k *= m
		h ^= k
		h *= m
		i += 8
	}

	remaining := data[i:]
	switch len(remaining) {
	case 7:
		h ^= uint64(remaining[6]) << 48
		fallthrough
	case 6:
		h ^= uint64(remaining[5]) << 40
		fallthrough
	case 5:
		h ^= uint64(remaining[4]) << 32
		fallthrough
	case 4:
		h ^= uint64(remaining[3]) << 24
		fallthrough
	case 3:
		h ^= uint64(remaining[2]) << 16
		fallthrough
	case 2:
		h ^= uint64(remaining[1]) << 8
		fallthrough
	case 1:
		h ^= uint64(remaining[0])
		h *= m
	}

	h ^= h >> r
	h *= m
	h ^= h >> r
	return h
}

func murmur64B(data []byte, seed uint32) uint64 {
	const m uint32 = 0x5BD1E995
	const r uint32 = 24
	length := len(data)
	i := 0

	h1 := seed ^ uint32(length)
	h2 := uint32(seed >> 32)

	for length >= 8 {
		k1 := binary.LittleEndian.Uint32(data[i:])
		i += 4
		length -= 4
		k1 *= m
		k1 ^= k1 >> r
		k1 *= m
		h1 *= m
		h1 ^= k1

		k2 := binary.LittleEndian.Uint32(data[i:])
		i += 4
		length -= 4
		k2 *= m
		k2 ^= k2 >> r
		k2 *= m
		h2 *= m
		h2 ^= k2
	}

	if length >= 4 {
		k1 := binary.LittleEndian.Uint32(data[i:])
		i += 4
		length -= 4
		k1 *= m
		k1 ^= k1 >> r
		k1 *= m
		h1 *= m
		h1 ^= k1
	}

	if length > 0 {
		switch length {
		case 3:
			h2 ^= uint32(data[i+2]) << 16
			fallthrough
		case 2:
			h2 ^= uint32(data[i+1]) << 8
			fallthrough
		case 1:
			h2 ^= uint32(data[i])
			h2 *= m
		}
	}

	h1 ^= h2 >> 18
	h1 *= m
	h2 ^= h1 >> 22
	h2 *= m
	h1 ^= h2 >> 17
	h1 *= m
	h2 ^= h1 >> 19
	h2 *= m

	return (uint64(h1) << 32) | uint64(h2)
}

func murmur2Hash(text string, variant Variant, seed uint32, output OutputFormat) string {
	data := []byte(text)
	switch variant {
	case VarX86_32:
		value := murmur2X86_32(data, seed)
		if output == OutputNumber {
			return strconv.FormatUint(uint64(value), 10)
		}
		return fmt.Sprintf("%08x", value)
	case VarX86_32A:
		value := murmur2A(data, seed)
		if output == OutputNumber {
			return strconv.FormatUint(uint64(value), 10)
		}
		return fmt.Sprintf("%08x", value)
	case VarNeutral32, VarAligned32:
		value := murmur2X86_32(data, seed)
		if output == OutputNumber {
			return strconv.FormatUint(uint64(value), 10)
		}
		return fmt.Sprintf("%08x", value)
	case VarX64_64A:
		value := murmur64A(data, seed)
		if output == OutputNumber {
			return strconv.FormatUint(value, 10)
		}
		return fmt.Sprintf("%016x", value)
	case VarX86_64B:
		value := murmur64B(data, seed)
		if output == OutputNumber {
			return strconv.FormatUint(value, 10)
		}
		return fmt.Sprintf("%016x", value)
	default:
		return ""
	}
}

func main() {
	seed := parseSeed("0x2a")
	fmt.Println(murmur2Hash("hello", VarX86_32, seed, OutputNumber))
	fmt.Println(murmur2Hash("hello", VarX64_64A, seed, OutputHex))

	if murmur2Hash("", VarX86_32, 0, OutputHex) != "00000000" {
		panic("Murmur2 empty mismatch")
	}
	if murmur2Hash("hello", VarX86_32, 0, OutputHex) != "e56129cb" {
		panic("Murmur2 x86_32 mismatch")
	}
	if murmur2Hash("hello", VarX64_64A, 0, OutputHex) != "1e68d17c457bf117" {
		panic("Murmur2 x64_64a mismatch")
	}
	fmt.Println("MurmurHash2 tests passed")
}