Skip to content

Commit 2f293d0

Browse files
committed
Reduce max scale to preserve numeric accuracy
1 parent 66b5e2c commit 2f293d0

File tree

3 files changed

+46
-28
lines changed

3 files changed

+46
-28
lines changed

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

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public class ExponentialHistogramUtils {
2929
/** The number of bits used to represent the exponent of IEEE 754 double precision number. */
3030
private static final int EXPONENT_WIDTH = 11;
3131

32-
private static final double LOG_BASE2_E = 1D / Math.log(2);
32+
private static final double LN_2 = Math.log(2);
33+
private static final double LOG_BASE2_E = 1D / LN_2;
3334

3435
// Magic number, computed via log(4/3)/log(2^(2^-64)), but exact
3536
private static final long SCALE_UP_64_OFFSET = 7656090530189244512L;
@@ -82,35 +83,21 @@ public static int getMaximumScaleIncrease(long index) {
8283
}
8384

8485
public static double getUpperBucketBoundary(long index, int scale) {
85-
long nextIndex = index;
86-
if (index < Long.MAX_VALUE) {
87-
nextIndex++;
88-
}
89-
return getLowerBucketBoundary(nextIndex, scale);
86+
return getLowerBucketBoundary(index + 1, scale);
9087
}
9188

9289
public static double getLowerBucketBoundary(long index, int scale) {
93-
// TODO: handle numeric limits, implement exact algorithms with 128 bit precision
94-
double inverseFactor = Math.pow(2, -scale);
95-
return Math.pow(2, inverseFactor * index);
90+
// TODO: handle numeric limits, implement by splitting the index into two 32 bit integers
91+
double inverseFactor = Math.scalb(LN_2, -scale);
92+
return Math.exp(inverseFactor * index);
9693
}
9794

9895
public static double getPointOfLeastRelativeError(long bucketIndex, int scale) {
9996
// TODO: handle numeric limits, implement exact algorithms with 128 bit precision
100-
double inverseFactor = Math.pow(2, -scale);
101-
return Math.pow(2, inverseFactor * (bucketIndex + 1 / 3.0));
97+
double inverseFactor = Math.scalb(LN_2, -scale);
98+
return Math.exp(inverseFactor * (bucketIndex + 1/3.0));
10299
}
103100

104-
/**
105-
* Compute the index for the given value.
106-
*
107-
* <p>The algorithm to retrieve the index is specified in the <a
108-
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md#exponential-buckets">OpenTelemetry
109-
* specification</a>.
110-
*
111-
* @param value Measured value (must be non-zero).
112-
* @return the index of the bucket which the value maps to.
113-
*/
114101
static long computeIndex(double value, int scale) {
115102
double absValue = Math.abs(value);
116103
// For positive scales, compute the index by logarithm, which is simpler but may be
@@ -135,7 +122,8 @@ static long computeIndex(double value, int scale) {
135122
* Scales: Use the Logarithm Function</a>
136123
*/
137124
private static long getIndexByLogarithm(double value, int scale) {
138-
return (long) Math.ceil(Math.log(value) * computeScaleFactor(scale)) - 1;
125+
double scaleFactor = Math.scalb(LOG_BASE2_E, scale);
126+
return (long) Math.ceil(Math.scalb(Math.log(value) * LOG_BASE2_E, scale)) - 1;
139127
}
140128

141129
/**
@@ -159,7 +147,4 @@ private static long mapToIndexScaleZero(double value) {
159147
return ieeeExponent;
160148
}
161149

162-
private static double computeScaleFactor(int scale) {
163-
return Math.scalb(LOG_BASE2_E, scale);
164-
}
165150
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111

1212
public final class FixedSizeExponentialHistogram implements ExponentialHistogramBuilder, ExponentialHistogram {
1313

14-
// scale of 52 is the largest scale being able to represent the smallest and largest double numbers
15-
// while giving a relative error less
16-
public static final int DEFAULT_BUCKET_SCALE = 52;
14+
// scale of 38 is the largest scale where the index computation doesn't suffer much rounding
15+
// if we want to use something larger, we'll have to rework the math
16+
public static final int DEFAULT_BUCKET_SCALE = 38;
1717

1818
private final long[] bucketIndices;
1919
private final long[] bucketCounts;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.exponentialhistogram;
11+
12+
import org.elasticsearch.test.ESTestCase;
13+
14+
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
15+
import static org.hamcrest.Matchers.lessThanOrEqualTo;
16+
17+
public class ExponentialHistogramGeneratorTests extends ESTestCase {
18+
19+
public void testVeryLargeValue() {
20+
double value = Double.MAX_VALUE/10;
21+
ExponentialHistogram histo = ExponentialHistogramGenerator.createFor(value);
22+
23+
long index = histo.positiveBuckets().peekIndex();
24+
int scale = histo.scale();
25+
26+
double lowerBound = ExponentialHistogramUtils.getLowerBucketBoundary(index, scale);
27+
double upperBound = ExponentialHistogramUtils.getUpperBucketBoundary(index, scale);
28+
29+
assertThat("Lower bucket boundary should be smaller than value", lowerBound, lessThanOrEqualTo(value));
30+
assertThat("Upper bucket boundary should be greater than value", upperBound, greaterThanOrEqualTo(value));
31+
}
32+
33+
}

0 commit comments

Comments
 (0)