{"record":{"id":"7de78696aff80e64","repo":"elastic/elasticsearch","slug":"q-should-be-in-0-1-got-q","errorCode":null,"errorMessage":"q should be in [0,1], got ${q}","messagePattern":"q should be in \\[0,1\\], got (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"libs/tdigest/src/main/java/org/elasticsearch/tdigest/AVLTreeDigest.java","lineNumber":302,"sourceCode":"            }\n            r += a.count();\n\n            // for the last element, assume right width is same as left\n            if (x < b.mean() + right) {\n                return (r + b.count() * interpolate(x, b.mean() - right, b.mean() + right)) / count;\n            }\n            return 1;\n        }\n    }\n\n    /**\n     * @param q The quantile desired.  Can be in the range [0,1].\n     * @return The minimum value x such that we think that the proportion of samples is &le; x is q.\n     */\n    @Override\n    public double quantile(double q) {\n        if (q < 0 || q > 1) {\n            throw new IllegalArgumentException(\"q should be in [0,1], got \" + q);\n        }\n\n        AVLGroupTree values = summary;\n        if (values.isEmpty()) {\n            // no centroids means no data, no way to get a quantile\n            return Double.NaN;\n        } else if (values.size() == 1) {\n            // with one data point, all quantiles lead to Rome\n            return values.iterator().next().mean();\n        }\n\n        // if values were stored in a sorted array, index would be the offset we are interested in\n        final double index = q * count;\n\n        // deal with min and max as a special case singletons\n        if (index <= 0) {\n            return min;\n        }","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/tdigest/src/main/java/org/elasticsearch/tdigest/AVLTreeDigest.java#L284-L320","documentation":"AVLTreeDigest.quantile(q) computes the value at quantile q. The contract requires q in the closed interval [0,1]; anything below 0 or above 1 throws IllegalArgumentException immediately, before any centroid lookup. With empty data it returns NaN, with a single centroid it returns that mean.","triggerScenarios":"Calling tdigest.quantile(q) where q is negative, > 1, NaN, or otherwise outside [0,1]. Common when q is computed from a ratio that can exceed the unit interval or from user-supplied percentiles divided incorrectly.","commonSituations":"Passing a percentile like 99 meaning 99% instead of 0.99; dividing by zero or null; computing q from an unbounded aggregation; feeding NaN from an upstream computation.","solutions":["Convert percent-in to fraction: divide by 100.0 before calling quantile","Clamp q to [0,1]: Math.max(0.0, Math.min(1.0, q))","Reject or skip when Double.isNaN(q)","Audit the upstream arithmetic that produced q"],"exampleFix":"// before\ndouble q = percentile; // percentile = 99.0\ndouble v = digest.quantile(q); // throws\n// after\ndouble q = Math.max(0.0, Math.min(1.0, percentile / 100.0));\ndouble v = digest.quantile(q);","handlingStrategy":"validation","validationCode":"double q = percentile / 100.0;\nif (Double.isNaN(q) || q < 0.0 || q > 1.0) {\n    throw new IllegalArgumentException(\"quantile out of range: \" + q);\n}\ndouble v = digest.quantile(q);","typeGuard":"static boolean isValidQuantile(double q) {\n    return !Double.isNaN(q) && q >= 0.0 && q <= 1.0;\n}","tryCatchPattern":"try { digest.quantile(q); }\ncatch (IllegalArgumentException e) { /* clamp or skip */ }","preventionTips":["Always convert percent to fraction (/100)","Clamp at the boundary instead of trusting callers","Guard NaN explicitly"],"tags":["tdigest","quantile","math","input-validation"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}