diff --git a/tdigest/include/tdigest_impl.hpp b/tdigest/include/tdigest_impl.hpp index 065e3ef1..2fb677da 100644 --- a/tdigest/include/tdigest_impl.hpp +++ b/tdigest/include/tdigest_impl.hpp @@ -70,6 +70,9 @@ void tdigest::update(T value) { template void tdigest::merge(const tdigest& other) { if (other.is_empty()) return; + // Extreme centroid means are not the true min/max when those centroids have weight > 1. + min_ = std::min(min_, other.min_); + max_ = std::max(max_, other.max_); vector_centroid tmp(buffer_.get_allocator()); tmp.reserve(buffer_.size() + centroids_.size() + other.buffer_.size() + other.centroids_.size()); for (const T value: buffer_) tmp.push_back(centroid(value, 1)); diff --git a/tdigest/test/tdigest_test.cpp b/tdigest/test/tdigest_test.cpp index 07d6185f..d187b749 100644 --- a/tdigest/test/tdigest_test.cpp +++ b/tdigest/test/tdigest_test.cpp @@ -168,6 +168,31 @@ TEST_CASE("merge small", "[tdigest]") { REQUIRE(td1.get_rank(3.01) == 1); } +TEST_CASE("merge preserves deserialized min max with weighted tails", "[tdigest]") { + tdigest_double source(100); + source.update(0); + source.update(50); + source.update(90); + auto bytes = source.serialize(); + write_bytes(bytes, min_offset, -1.0); + write_bytes(bytes, max_offset, 100.0); + auto other = tdigest_double::deserialize(bytes.data(), bytes.size()); + REQUIRE(other.get_min_value() == -1.0); + REQUIRE(other.get_max_value() == 100.0); + + tdigest_double empty(100); + empty.merge(other); + REQUIRE(empty.get_min_value() == -1.0); + REQUIRE(empty.get_max_value() == 100.0); + + tdigest_double left(100); + left.update(10); + left.update(20); + left.merge(other); + REQUIRE(left.get_min_value() == -1.0); + REQUIRE(left.get_max_value() == 100.0); +} + TEST_CASE("merge large", "[tdigest]") { const size_t n = 10000; tdigest_double td1;