2,525 bytes · the Python implementation · view raw
import math
from typing import Sequence
from .stats_percentile_types import PercentileMethod
POW10 = [1.0, 10.0, 100.0, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12]
def _finite(value: object) -> bool:
return isinstance(value, (int, float)) andnot isinstance(value, bool) and math.isfinite(value)
def _round_to(x: float, decimals: int) -> float:
# Half away from zero on the binary64 value, then -0 becomes 0. Python's# round() is half-even and would give round(0.5) == 0; this does not.
scale = POW10[decimals]
y = abs(x) * scale
r = float(math.floor(y))
if y - r >= 0.5:
r += 1.0
out = r / scale
return (-out if x < 0else out) + 0.0def percentile(values: Sequence[float], p: float, method: PercentileMethod, decimals: int) -> float:
"""The p-th percentile of ``values`` by a named method. There is no single "95th percentile": the caller names the definition, so a dashboard and a billing job cannot quietly disagree. """if isinstance(values, (str, bytes)) ornot isinstance(values, (list, tuple)):
raise TypeError("values must be a list of numbers")
if len(values) == 0:
raise ValueError("values must not be empty")
for v in values:
ifnot _finite(v):
raise TypeError("values must be finite numbers, received %r" % (v,))
ifnot _finite(p) or p < 0or p > 100:
raise ValueError("p must be between 0 and 100, received %r" % (p,))
if isinstance(decimals, bool) ornot isinstance(decimals, int) or decimals < 0or decimals > 12:
raise ValueError("decimals must be a whole number from 0 to 12, received %r" % (decimals,))
# float() first so integer inputs follow exactly the binary64 path the# other languages take.
ordered = sorted(float(v) for v in values)
n = len(ordered)
p = float(p)
if method == "nearest-rank":
# The smallest value with at least p% of the sample at or below it.
rank = math.ceil((p * n) / 100.0)
if rank < 1:
rank = 1
result = ordered[rank - 1]
elif method == "linear":
# Hyndman & Fan type 7: zero-based position (n - 1) * p / 100.
h = ((n - 1) * p) / 100.0
lo = math.floor(h)
if lo >= n - 1:
result = ordered[n - 1]
else:
result = ordered[lo] + (h - lo) * (ordered[lo + 1] - ordered[lo])
else:
raise ValueError('unknown percentile method "%s"' % (method,))
return _round_to(result, decimals)