This is an automated email from the ASF dual-hosted git repository.
tisonkun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datasketches-rust.git
The following commit(s) were added to refs/heads/main by this push:
new 1158716 fix(tdigest): preserve min/max when merging digests (#279)
1158716 is described below
commit 1158716f086944af9a36c49cddf3c098516fbb0c
Author: Jaideep Pyne <[email protected]>
AuthorDate: Fri Sep 18 14:28:20 2026 +0530
fix(tdigest): preserve min/max when merging digests (#279)
Co-authored-by: jaideeppyne <[email protected]>
---
CHANGELOG.md | 1 +
datasketches/src/tdigest/sketch.rs | 5 +++++
tests-integration/tests/tdigest_test/sketch.rs | 28 ++++++++++++++++++++++++++
3 files changed, 34 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fcfb676..6ea1c96 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@ All significant changes to this project will be documented in
this file.
### Bug fixes
+* Fix T-Digest `merge` so it preserves `min`/`max` from the other digest
instead of re-deriving them from centroid means after compression.
* T-Digest deserialization now rejects unknown or conflicting flags, reversed
extrema, out-of-range values, unsorted centroids, and non-empty images without
stored values.
## v0.5.0 (2026-09-04)
diff --git a/datasketches/src/tdigest/sketch.rs
b/datasketches/src/tdigest/sketch.rs
index d496a9c..1c23867 100644
--- a/datasketches/src/tdigest/sketch.rs
+++ b/datasketches/src/tdigest/sketch.rs
@@ -339,6 +339,11 @@ impl TDigestMut {
return;
}
+ // Preserve true extrema from `other`. Compression only sees centroid
means, which can
+ // differ from `min`/`max` after ordinary compression or
deserialization.
+ self.min = self.min.min(other.min);
+ self.max = self.max.max(other.max);
+
let self_unmerged_weight = self.buffer.unmerged_len() as u64;
let centroids = std::mem::take(&mut
self.buffer).into_merged_centroids(&other.buffer);
self.compress_sorted_centroids(centroids, self_unmerged_weight +
other.total_weight())
diff --git a/tests-integration/tests/tdigest_test/sketch.rs
b/tests-integration/tests/tdigest_test/sketch.rs
index 302cc4c..d13c2ba 100644
--- a/tests-integration/tests/tdigest_test/sketch.rs
+++ b/tests-integration/tests/tdigest_test/sketch.rs
@@ -409,3 +409,31 @@ fn test_rank_left_tail_is_a_fraction_of_the_total_weight()
{
assert_that!(pmf[1], near(0.8, 1e-12));
assert_that!(pmf[2], near(0.1, 1e-12));
}
+
+#[test]
+fn test_merge_preserves_min_max_from_other() {
+ // Heavy extreme centroids are legal after deserialization: `min`/`max`
can differ from the
+ // first and last centroid means. Merge must keep those stored extrema,
not re-derive them.
+ let other = deserialize_with_centroids(100, 0.0, 100.0, &[(10.0, 10),
(50.0, 10), (90.0, 10)]);
+ assert_eq!(other.min_value(), Some(0.0));
+ assert_eq!(other.max_value(), Some(100.0));
+
+ let mut empty = TDigestMut::new(100).unwrap();
+ empty.merge(&other);
+ assert_eq!(empty.min_value(), Some(0.0));
+ assert_eq!(empty.max_value(), Some(100.0));
+ assert_eq!(empty.quantile(0.0), Some(0.0));
+ assert_eq!(empty.quantile(1.0), Some(100.0));
+ assert_eq!(empty.rank(0.0), Some(0.5 / 30.0));
+ assert_eq!(empty.rank(100.0), Some(1.0 - 0.5 / 30.0));
+ assert_eq!(empty.rank(-1.0), Some(0.0));
+ assert_eq!(empty.rank(101.0), Some(1.0));
+
+ let mut left = deserialize_with_centroids(100, 5.0, 40.0, &[(10.0, 4),
(20.0, 4), (30.0, 4)]);
+ let right = deserialize_with_centroids(100, -10.0, 80.0, &[(0.0, 4),
(40.0, 4), (70.0, 4)]);
+ left.merge(&right);
+ assert_eq!(left.min_value(), Some(-10.0));
+ assert_eq!(left.max_value(), Some(80.0));
+ assert_eq!(left.quantile(0.0), Some(-10.0));
+ assert_eq!(left.quantile(1.0), Some(80.0));
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]