@@ -45,32 +45,88 @@ public static double getQuantile(ExponentialHistogram histo, double quantile) {
45
45
long upperRank = (long ) Math .ceil (exactRank );
46
46
double upperFactor = exactRank - lowerRank ;
47
47
48
- // TODO: This can be optimized to iterate over the buckets once instead of twice.
49
- return getElementAtRank ( histo , lowerRank , negCount , zeroCount ) * ( 1 - upperFactor ) + getElementAtRank (
50
- histo ,
51
- upperRank ,
52
- negCount ,
53
- zeroCount
54
- ) * upperFactor ;
48
+ ValueAndPreviousValue values = getElementAtRank ( histo , upperRank );
49
+
50
+ if ( lowerRank == upperRank ) {
51
+ return values . valueAtRank ();
52
+ } else {
53
+ return values . valueAtPreviousRank () * ( 1 - upperFactor ) + values . valueAtRank () * upperFactor ;
54
+ }
55
55
}
56
56
57
- private static double getElementAtRank (ExponentialHistogram histo , long rank , long negCount , long zeroCount ) {
58
- if (rank < negCount ) {
59
- return -getBucketMidpointForRank (histo .negativeBuckets ().iterator (), (negCount - 1 ) - rank );
60
- } else if (rank < (negCount + zeroCount )) {
61
- return 0.0 ;
57
+ /**
58
+ * @param valueAtPreviousRank the value at the rank before the desired rank, NaN if not applicable.
59
+ * @param valueAtRank the value at the desired rank
60
+ */
61
+ private record ValueAndPreviousValue (double valueAtPreviousRank , double valueAtRank
62
+ ) {
63
+ ValueAndPreviousValue negateAndSwap () {
64
+ return new ValueAndPreviousValue (-valueAtRank , -valueAtPreviousRank );
65
+ }
66
+ }
67
+
68
+ private static ValueAndPreviousValue getElementAtRank (ExponentialHistogram histo , long rank ) {
69
+ long negativeValuesCount = histo .negativeBuckets ().valueCount ();
70
+ long zeroCount = histo .zeroBucket ().count ();
71
+ if (rank < negativeValuesCount ) {
72
+ if (negativeValuesCount == 1 ) {
73
+ return new ValueAndPreviousValue (Double .NaN , -getFirstBucketMidpoint (histo .negativeBuckets ()));
74
+ } else {
75
+ return getBucketMidpointForRank (histo .negativeBuckets ().iterator (), negativeValuesCount - rank - 1 ).negateAndSwap ();
76
+ }
77
+ } else if (rank < (negativeValuesCount + zeroCount )) {
78
+ if (rank == negativeValuesCount ) {
79
+ // the element at the previous rank falls into the negative bucket range
80
+ return new ValueAndPreviousValue (-getFirstBucketMidpoint (histo .negativeBuckets ()), 0.0 );
81
+ } else {
82
+ return new ValueAndPreviousValue (0.0 , 0.0 );
83
+ }
84
+ } else {
85
+ ValueAndPreviousValue result = getBucketMidpointForRank (histo .positiveBuckets ().iterator (), rank - negativeValuesCount - zeroCount );
86
+ if ( (rank -1 ) < negativeValuesCount ) {
87
+ // previous value falls into the negative bucket range or is -1
88
+ return new ValueAndPreviousValue (-getFirstBucketMidpoint (histo .negativeBuckets ()), result .valueAtRank );
89
+ } else if ( (rank -1 ) < (negativeValuesCount + zeroCount ) ) {
90
+ // previous value falls into the zero bucket
91
+ return new ValueAndPreviousValue (0.0 , result .valueAtRank );
92
+ } else {
93
+ return result ;
94
+ }
95
+ }
96
+ }
97
+
98
+ private static double getFirstBucketMidpoint (ExponentialHistogram .Buckets buckets ) {
99
+ CopyableBucketIterator iterator = buckets .iterator ();
100
+ if (iterator .hasNext ()) {
101
+ return ExponentialScaleUtils .getPointOfLeastRelativeError (iterator .peekIndex (), iterator .scale ());
62
102
} else {
63
- return getBucketMidpointForRank ( histo . positiveBuckets (). iterator (), rank - ( negCount + zeroCount )) ;
103
+ return Double . NaN ;
64
104
}
65
105
}
66
106
67
- private static double getBucketMidpointForRank (BucketIterator buckets , long rank ) {
107
+ private static ValueAndPreviousValue getBucketMidpointForRank (BucketIterator buckets , long rank ) {
108
+ long prevIndex = Long .MIN_VALUE ;
68
109
long seenCount = 0 ;
69
110
while (buckets .hasNext ()) {
70
111
seenCount += buckets .peekCount ();
71
112
if (rank < seenCount ) {
72
- return ExponentialScaleUtils .getPointOfLeastRelativeError (buckets .peekIndex (), buckets .scale ());
113
+ double center = ExponentialScaleUtils .getPointOfLeastRelativeError (buckets .peekIndex (), buckets .scale ());
114
+ double prevCenter ;
115
+ if (rank > 0 ) {
116
+ if (buckets .peekCount () > 1 ) {
117
+ // element at previous rank is in same bucket
118
+ prevCenter = center ;
119
+ } else {
120
+ // element at previous rank is in the previous bucket
121
+ prevCenter = ExponentialScaleUtils .getPointOfLeastRelativeError (prevIndex , buckets .scale ());
122
+ }
123
+ } else {
124
+ // there is no previous element
125
+ prevCenter = Double .NaN ;
126
+ }
127
+ return new ValueAndPreviousValue (prevCenter , center );
73
128
}
129
+ prevIndex = buckets .peekIndex ();
74
130
buckets .advance ();
75
131
}
76
132
throw new IllegalStateException ("The total number of elements in the buckets is less than the desired rank." );
0 commit comments