Skip to content

Commit 95166d9

Browse files
committed
Clamp before interpolation
1 parent afb29d6 commit 95166d9

File tree

2 files changed

+28
-1
lines changed

2 files changed

+28
-1
lines changed

libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramQuantile.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,16 @@ public static double getQuantile(ExponentialHistogram histo, double quantile) {
6161
double upperFactor = exactRank - lowerRank;
6262

6363
ValueAndPreviousValue values = getElementAtRank(histo, upperRank);
64+
// Ensure we don't return values outside the histogram's range
65+
values = values.clampTo(histo.min(), histo.max());
6466

6567
double result;
6668
if (lowerRank == upperRank) {
6769
result = values.valueAtRank();
6870
} else {
6971
result = values.valueAtPreviousRank() * (1 - upperFactor) + values.valueAtRank() * upperFactor;
7072
}
71-
return removeNegativeZero(Math.clamp(result, histo.min(), histo.max()));
73+
return removeNegativeZero(result);
7274
}
7375

7476
private static double removeNegativeZero(double result) {
@@ -159,8 +161,16 @@ private static ValueAndPreviousValue getBucketMidpointForRank(BucketIterator buc
159161
* @param valueAtRank the value at the desired rank
160162
*/
161163
private record ValueAndPreviousValue(double valueAtPreviousRank, double valueAtRank) {
164+
162165
ValueAndPreviousValue negateAndSwap() {
163166
return new ValueAndPreviousValue(-valueAtRank, -valueAtPreviousRank);
164167
}
168+
169+
ValueAndPreviousValue clampTo(double min, double max) {
170+
return new ValueAndPreviousValue(
171+
Math.clamp(valueAtPreviousRank, min, max),
172+
Math.clamp(valueAtRank, min, max)
173+
);
174+
}
165175
}
166176
}

libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/QuantileAccuracyTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,23 @@ public void testPercentilesClampedToMinMax() {
7373
assertThat(p99, equalTo(0.00001));
7474
}
7575

76+
public void testMinMaxClampedPercentileaccurace() {
77+
ExponentialHistogram histogram = createAutoReleasedHistogram(
78+
b -> b.scale(0)
79+
.setPositiveBucket(0, 1) // bucket 0 covers (1, 2]
80+
.setPositiveBucket(1, 1) // bucket 1 covers (2, 4]
81+
.min(1.1)
82+
.max(2.1)
83+
);
84+
85+
// The 0.5 percentile linearly interpolates between the two buckets.
86+
// For the (1, 2] bucket, the point of least relative error will be used (1.33333)
87+
// For the (1, 2] bucket, the max of the histogram should be used instead (2.1)
88+
double expectedResult = (4.0/3 + 2.1) / 2;
89+
double p50 = ExponentialHistogramQuantile.getQuantile(histogram, 0.5);
90+
assertThat(p50, equalTo(expectedResult));
91+
}
92+
7693
public void testUniformDistribution() {
7794
testDistributionQuantileAccuracy(new UniformRealDistribution(new Well19937c(randomInt()), 0, 100));
7895
}

0 commit comments

Comments
 (0)