2121
2222package org .elasticsearch .exponentialhistogram ;
2323
24+ import static org .elasticsearch .exponentialhistogram .ExponentialHistogram .MAX_INDEX ;
2425import static org .elasticsearch .exponentialhistogram .ExponentialHistogram .MAX_SCALE ;
2526import static org .elasticsearch .exponentialhistogram .ExponentialHistogram .MIN_INDEX ;
2627import static org .elasticsearch .exponentialhistogram .ExponentialHistogram .MIN_SCALE ;
3435 * To allow efficient comparison with bucket boundaries, this class internally
3536 * represents the zero threshold as a exponential histogram bucket index with a scale,
3637 * computed via {@link ExponentialScaleUtils#computeIndex(double, int)}.
37- *
38- * @param index The index used with the scale to determine the zero threshold.
39- * @param scale The scale used with the index to determine the zero threshold.
40- * @param count The number of values in the zero bucket.
4138 */
42- public record ZeroBucket ( long index , int scale , long count ) {
39+ public final class ZeroBucket {
4340
41+ /**
42+ * The exponential histogram scale used for {@link #index}
43+ */
44+ private final int scale ;
45+
46+ /**
47+ * The exponential histogram bucket index whose upper boundary corresponds to the zero threshold.
48+ * Might be computed lazily from {@link #realThreshold}, uses {@link Long#MAX_VALUE} as placeholder in this case.
49+ */
50+ private long index ;
51+
52+ /**
53+ * Might be computed lazily from {@link #realThreshold}, uses {@link Long#MAX_VALUE} as placeholder in this case.
54+ */
55+ private double realThreshold ;
56+
57+ private final long count ;
4458 // A singleton for an empty zero bucket with the smallest possible threshold.
4559 private static final ZeroBucket MINIMAL_EMPTY = new ZeroBucket (MIN_INDEX , MIN_SCALE , 0 );
4660
@@ -51,7 +65,27 @@ public record ZeroBucket(long index, int scale, long count) {
5165 * @param count The number of values in the bucket.
5266 */
5367 public ZeroBucket (double zeroThreshold , long count ) {
54- this (computeIndex (zeroThreshold , MAX_SCALE ) + 1 , MAX_SCALE , count );
68+ assert zeroThreshold >= 0.0 : "zeroThreshold must not be negative" ;
69+ this .index = Long .MAX_VALUE ; // compute lazily when needed
70+ this .scale = MAX_SCALE ;
71+ this .realThreshold = zeroThreshold ;
72+ this .count = count ;
73+ }
74+
75+ ZeroBucket (long index , int scale , long count ) {
76+ assert index >= MIN_INDEX && index <= MAX_INDEX : "index must be in range [" + MIN_INDEX + ", " + MAX_INDEX + "]" ;
77+ assert scale >= MIN_SCALE && scale <= MAX_SCALE : "scale must be in range [" + MIN_SCALE + ", " + MAX_SCALE + "]" ;
78+ this .index = index ;
79+ this .scale = scale ;
80+ this .realThreshold = Double .NaN ; // compute lazily when needed
81+ this .count = count ;
82+ }
83+
84+ private ZeroBucket (double realThreshold , long index , int scale , long count ) {
85+ this .index = index ;
86+ this .scale = scale ;
87+ this .realThreshold = realThreshold ; // compute lazily when needed
88+ this .count = count ;
5589 }
5690
5791 /**
@@ -71,8 +105,33 @@ public static ZeroBucket minimalWithCount(long count) {
71105 if (count == 0 ) {
72106 return MINIMAL_EMPTY ;
73107 } else {
74- return new ZeroBucket (MINIMAL_EMPTY .index , MINIMAL_EMPTY .scale (), count );
108+ return new ZeroBucket (MINIMAL_EMPTY .zeroThreshold (), MINIMAL_EMPTY .index (), MINIMAL_EMPTY .scale (), count );
109+ }
110+ }
111+
112+ /**
113+ * @return The value of the zero threshold.
114+ */
115+ public double zeroThreshold () {
116+ if (Double .isNaN (realThreshold )) {
117+ realThreshold = exponentiallyScaledToDoubleValue (index (), scale ());
75118 }
119+ return realThreshold ;
120+ }
121+
122+ public long index () {
123+ if (index == Long .MAX_VALUE ) {
124+ index = computeIndex (zeroThreshold (), scale ()) + 1 ;
125+ }
126+ return index ;
127+ }
128+
129+ public int scale () {
130+ return scale ;
131+ }
132+
133+ public long count () {
134+ return count ;
76135 }
77136
78137 /**
@@ -95,9 +154,9 @@ public ZeroBucket merge(ZeroBucket other) {
95154 long totalCount = count + other .count ;
96155 // Both are populated, so we need to use the higher zero-threshold.
97156 if (this .compareZeroThreshold (other ) >= 0 ) {
98- return new ZeroBucket (index , scale , totalCount );
157+ return new ZeroBucket (realThreshold , index , scale , totalCount );
99158 } else {
100- return new ZeroBucket (other .index , other .scale , totalCount );
159+ return new ZeroBucket (other .realThreshold , other . index , other .scale , totalCount );
101160 }
102161 }
103162 }
@@ -129,14 +188,7 @@ public ZeroBucket collapseOverlappingBucketsForAll(BucketIterator... bucketItera
129188 * equal to, or greater than the other's.
130189 */
131190 public int compareZeroThreshold (ZeroBucket other ) {
132- return compareExponentiallyScaledValues (index , scale , other .index , other .scale );
133- }
134-
135- /**
136- * @return The value of the zero threshold.
137- */
138- public double zeroThreshold () {
139- return exponentiallyScaledToDoubleValue (index , scale );
191+ return compareExponentiallyScaledValues (index (), scale (), other .index (), other .scale ());
140192 }
141193
142194 /**
@@ -150,7 +202,7 @@ public ZeroBucket collapseOverlappingBuckets(BucketIterator buckets) {
150202
151203 long collapsedCount = 0 ;
152204 long highestCollapsedIndex = 0 ;
153- while (buckets .hasNext () && compareExponentiallyScaledValues (buckets .peekIndex (), buckets .scale (), index , scale ) < 0 ) {
205+ while (buckets .hasNext () && compareExponentiallyScaledValues (buckets .peekIndex (), buckets .scale (), index () , scale () ) < 0 ) {
154206 highestCollapsedIndex = buckets .peekIndex ();
155207 collapsedCount += buckets .peekCount ();
156208 buckets .advance ();
@@ -161,9 +213,9 @@ public ZeroBucket collapseOverlappingBuckets(BucketIterator buckets) {
161213 long newZeroCount = count + collapsedCount ;
162214 // +1 because we need to adjust the zero threshold to the upper boundary of the collapsed bucket
163215 long collapsedUpperBoundIndex = highestCollapsedIndex + 1 ;
164- if (compareExponentiallyScaledValues (index , scale , collapsedUpperBoundIndex , buckets .scale ()) >= 0 ) {
216+ if (compareExponentiallyScaledValues (index () , scale () , collapsedUpperBoundIndex , buckets .scale ()) >= 0 ) {
165217 // Our current zero-threshold is larger than the upper boundary of the largest collapsed bucket, so we keep it.
166- return new ZeroBucket (index , scale , newZeroCount );
218+ return new ZeroBucket (realThreshold , index , scale , newZeroCount );
167219 } else {
168220 return new ZeroBucket (collapsedUpperBoundIndex , buckets .scale (), newZeroCount );
169221 }
0 commit comments