package analytics import ( "hash/fnv" "math" "strconv" "testing" ) func h64(s string) uint64 { f := fnv.New64a(); f.Write([]byte(s)); return f.Sum64() } func TestHLLEstimatesWithinThreeSigma(t *testing.T) { for _, n := range []int{1000, 10000, 100000} { var h HLL for i := 0; i < n; i++ { h.Add(h64("v" + itoa(i))) } got := h.Count() sigma := 1.04 / math.Sqrt(hllRegisters) * float64(n) if math.Abs(got-float64(n)) > 3*sigma { t.Errorf("n=%d: estimate %.0f off by more than 3σ (%.0f)", n, got, 3*sigma) } } } func TestHLLMergeIsUnionAndIdempotent(t *testing.T) { var a, b HLL for i := 0; i < 5000; i++ { a.Add(h64("a" + itoa(i))) b.Add(h64("b" + itoa(i))) } var ab HLL ab.Merge(&a) ab.Merge(&b) ab.Merge(&b) // idempotent if got := ab.Count(); math.Abs(got-10000) > 3*(1.04/math.Sqrt(hllRegisters))*10000 { t.Fatalf("union estimate %.0f", got) } var ba HLL ba.Merge(&b) ba.Merge(&a) if ab.reg != ba.reg { t.Fatal("merge must be commutative") } } func TestHLLEncodeRoundTripAndVersion(t *testing.T) { var h HLL h.Add(h64("x")) enc := h.Encode() if enc[:2] != "AQ" { // base64 of a leading 0x01 byte t.Fatalf("version prefix missing: %q", enc[:4]) } d, err := DecodeHLL(enc) if err != nil || d.reg != h.reg { t.Fatalf("round trip: %v", err) } if e, err := DecodeHLL(""); err != nil || !e.Empty() { t.Fatal("empty string must decode to an empty sketch") } if _, err := DecodeHLL("AgAA"); err == nil { t.Fatal("unknown version must error") } } // itoa is the brief's crude helper, replaced with strconv.Itoa: the // original digit-reversed encoding broke TestTopKExactBelowCapAndBoundedAbove's // key lookups (itoa(99) produced "990000", not "99") and had weaker // diversity in leading digits than strconv's ordinary decimal, which // stressed the HLL register-index entropy (see hll.go's mix64 note). func itoa(i int) string { return strconv.Itoa(i) }