// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT package stats import ( "math" "strings" "testing" ) // TestQuantile pins linear interpolation between order statistics. func TestQuantile(t *testing.T) { a := mustFloats(t, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 10) q, err := Quantile(a, []float64{0.25}) if err != nil { t.Fatal(err) } // Linear interpolation: position 0.25*(9)=2.25 lands between 3 and 4. if v := q.FloatAt(0); math.Abs(v-3.25) > 1e-9 { t.Errorf("q25: %v, want 3.25", v) } med, _ := Quantile(a, []float64{0.5}) if v := med.FloatAt(0); math.Abs(v-5.5) > 1e-9 { t.Errorf("median quantile: %v", v) } } // TestQuantileDegenerateInputs pins the guard that used to panic: // Quantile on an empty sample. func TestQuantileDegenerateInputs(t *testing.T) { empty := mustFloats(t, []float64{}, 0) if _, err := Quantile(empty, []float64{0.5}); err == nil { t.Error("Quantile empty: expected error") } } // TestBinCounts counts values in equally wide bins. func TestBinCounts(t *testing.T) { vals := mustFloats(t, []float64{-3, 5, 12, 25}, 4) counts, err := BinCounts(vals, 2) if err != nil { t.Fatal(err) } if counts.Len() != 2 { t.Fatalf("counts len: %d", counts.Len()) } if c1 := counts.FloatAt(0); c1 < 1 { t.Errorf("first bin empty: %v", counts.RawFloats()) } } // TestComplexStatsErrors pins the refusals for complex input: no // ordering, so no median, standard deviation or histogram. func TestComplexStatsErrors(t *testing.T) { c := mustComplexes(t, []complex128{complex(1, 1)}, 1) if _, err := Median(c); err == nil || !strings.Contains(err.Error(), "no median") { t.Fatalf("Median complex: %v", err) } if _, err := Std(c); err == nil || !strings.Contains(err.Error(), "no float standard deviation") { t.Fatalf("Std complex: %v", err) } if _, _, err := Histogram(c, 2); err == nil || !strings.Contains(err.Error(), "no histogram") { t.Fatalf("Histogram complex: %v", err) } }