Skip to content

Commit 7cd7b84

Browse files
committed
Normalize the left tail of TDigestDouble.getRank()
The left tail branch returned the interpolated weight without dividing by centroidsWeight_, unlike the mirror-image right tail branch and the reference implementation. Reachable via heapify() when the first centroid has weight greater than 1, making getRank() exceed its documented [0, 1] range.
1 parent d5cce9b commit 7cd7b84

2 files changed

Lines changed: 22 additions & 1 deletion

File tree

src/main/java/org/apache/datasketches/tdigest/TDigestDouble.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ public double getRank(final double value) {
192192
if (value < firstMean) {
193193
if ((firstMean - minValue_) > 0) {
194194
if (value == minValue_) { return 0.5 / centroidsWeight_; }
195-
return (1.0 + (((value - minValue_) / (firstMean - minValue_)) * ((centroidWeights_[0] / 2.0) - 1.0)));
195+
return (1.0 + (((value - minValue_) / (firstMean - minValue_))
196+
* ((centroidWeights_[0] / 2.0) - 1.0))) / centroidsWeight_;
196197
}
197198
return 0; // should never happen
198199
}

src/test/java/org/apache/datasketches/tdigest/TDigestDoubleTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,26 @@ public void deserializeNaNSingleValue() {
236236
assertThrows(SketchesArgumentException.class, () -> TDigestDouble.heapify(MemorySegment.ofArray(bytes)));
237237
}
238238

239+
@Test
240+
public void rankBelowFirstCentroidMean() {
241+
// the format allows a first centroid of weight greater than 1, so the left tail of
242+
// getRank() must stay normalized just like the right tail
243+
final byte[] bytes = serializeNonEmpty();
244+
MemorySegment.ofArray(bytes).set(ValueLayout.JAVA_DOUBLE_UNALIGNED, 16, -1); // min
245+
MemorySegment.ofArray(bytes).set(ValueLayout.JAVA_LONG_UNALIGNED, 40, 100L); // first weight
246+
final TDigestDouble td = TDigestDouble.heapify(MemorySegment.ofArray(bytes));
247+
final double totalWeight = td.getTotalWeight();
248+
assertEquals(td.getRank(-1), 0.5 / totalWeight);
249+
assertEquals(td.getRank(-0.5), (1.0 + (((100 / 2.0) - 1.0) * 0.5)) / totalWeight);
250+
double previous = 0;
251+
for (int i = 0; i <= 100; i++) {
252+
final double rank = td.getRank(-1 + (i / 100.0));
253+
assertTrue((rank >= 0) && (rank <= 1), "rank out of [0, 1]: " + rank);
254+
assertTrue(rank >= previous, "rank not monotonic: " + rank + " after " + previous);
255+
previous = rank;
256+
}
257+
}
258+
239259
@Test
240260
public void deserializeFromReferenceImplementationDouble() {
241261
final byte[] bytes = TestUtil.getFileBytes(resPath, "tdigest_ref_k100_n10000_double.sk");

0 commit comments

Comments
 (0)