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]

Reply via email to