metrics: replace round-based percentile with linear interpolation (B5)

The previous implementation used round((n-1) * pct), which under Python's
banker's rounding returned the upper-middle element on every even-length
array (e.g. p50 of [1,2,3,4] returned 3 instead of 2.5). All summary
JSONs were biased upward at p50 as a result. Match numpy.percentile's
default linear interpolation between the two adjacent sorted values.
This commit is contained in:
2026-05-23 21:00:24 +08:00
parent 0958823cdb
commit 0ed1ce200e

View File

@@ -101,7 +101,11 @@ def _stats(values: list[float | None]) -> dict[str, float] | None:
def _percentile(sorted_vals: list[float], pct: float) -> float:
if len(sorted_vals) == 1:
n = len(sorted_vals)
if n == 1:
return sorted_vals[0]
idx = round((len(sorted_vals) - 1) * pct)
return sorted_vals[idx]
rank = pct * (n - 1)
lo = int(rank)
hi = min(lo + 1, n - 1)
frac = rank - lo
return sorted_vals[lo] * (1 - frac) + sorted_vals[hi] * frac