datasketches 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/ext/datasketches/cpc_wrapper.cpp +8 -9
- data/ext/datasketches/fi_wrapper.cpp +4 -5
- data/ext/datasketches/hll_wrapper.cpp +9 -10
- data/ext/datasketches/kll_wrapper.cpp +13 -12
- data/ext/datasketches/theta_wrapper.cpp +11 -13
- data/ext/datasketches/vo_wrapper.cpp +1 -2
- data/lib/datasketches/version.rb +1 -1
- data/vendor/datasketches-cpp/CMakeLists.txt +5 -3
- data/vendor/datasketches-cpp/CODE_OF_CONDUCT.md +1 -1
- data/vendor/datasketches-cpp/LICENSE +14 -0
- data/vendor/datasketches-cpp/README.md +67 -73
- data/vendor/datasketches-cpp/benchmarks/CMakeLists.txt +52 -0
- data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch.cpp +153 -0
- data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch_serialization.cpp +161 -0
- data/vendor/datasketches-cpp/common/CMakeLists.txt +1 -0
- data/vendor/datasketches-cpp/common/include/binomial_bounds.hpp +2 -2
- data/vendor/datasketches-cpp/common/include/fdlibm_log.hpp +101 -0
- data/vendor/datasketches-cpp/common/include/serde.hpp +6 -0
- data/vendor/datasketches-cpp/common/test/CMakeLists.txt +44 -2
- data/vendor/datasketches-cpp/common/test/binomial_bounds_test.cpp +279 -0
- data/vendor/datasketches-cpp/common/test/deserialize_hardening_test.cpp +188 -0
- data/vendor/datasketches-cpp/count/include/count_min.hpp +17 -4
- data/vendor/datasketches-cpp/count/include/count_min_impl.hpp +65 -83
- data/vendor/datasketches-cpp/count/test/count_min_test.cpp +63 -7
- data/vendor/datasketches-cpp/cpc/include/compression_data.hpp +2 -0
- data/vendor/datasketches-cpp/cpc/include/cpc_compressor_impl.hpp +16 -7
- data/vendor/datasketches-cpp/cpc/include/cpc_sketch.hpp +1 -0
- data/vendor/datasketches-cpp/cpc/include/cpc_sketch_impl.hpp +54 -36
- data/vendor/datasketches-cpp/cpc/include/cpc_union_impl.hpp +27 -27
- data/vendor/datasketches-cpp/cpc/include/cpc_util.hpp +7 -7
- data/vendor/datasketches-cpp/cpc/include/icon_estimator.hpp +5 -5
- data/vendor/datasketches-cpp/cpc/include/u32_table_impl.hpp +16 -16
- data/vendor/datasketches-cpp/cpc/test/cpc_sketch_test.cpp +35 -0
- data/vendor/datasketches-cpp/fi/include/frequent_items_sketch.hpp +28 -3
- data/vendor/datasketches-cpp/fi/include/frequent_items_sketch_impl.hpp +41 -25
- data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map.hpp +3 -1
- data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map_impl.hpp +10 -5
- data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_serialize_for_java.cpp +24 -0
- data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_test.cpp +116 -0
- data/vendor/datasketches-cpp/filters/include/bloom_filter.hpp +1 -1
- data/vendor/datasketches-cpp/filters/include/bloom_filter_impl.hpp +32 -12
- data/vendor/datasketches-cpp/filters/test/bloom_filter_test.cpp +28 -1
- data/vendor/datasketches-cpp/hll/include/CouponHashSet-internal.hpp +1 -2
- data/vendor/datasketches-cpp/hll/include/CouponList-internal.hpp +19 -5
- data/vendor/datasketches-cpp/hll/include/CubicInterpolation-internal.hpp +4 -4
- data/vendor/datasketches-cpp/hll/include/HarmonicNumbers-internal.hpp +2 -1
- data/vendor/datasketches-cpp/hll/include/Hll4Array-internal.hpp +4 -4
- data/vendor/datasketches-cpp/hll/include/Hll4Array.hpp +1 -1
- data/vendor/datasketches-cpp/hll/include/Hll6Array-internal.hpp +3 -3
- data/vendor/datasketches-cpp/hll/include/Hll6Array.hpp +1 -1
- data/vendor/datasketches-cpp/hll/include/Hll8Array-internal.hpp +6 -3
- data/vendor/datasketches-cpp/hll/include/Hll8Array.hpp +1 -1
- data/vendor/datasketches-cpp/hll/include/HllArray-internal.hpp +35 -29
- data/vendor/datasketches-cpp/hll/include/HllArray.hpp +1 -1
- data/vendor/datasketches-cpp/hll/include/HllSketch-internal.hpp +3 -5
- data/vendor/datasketches-cpp/hll/include/HllSketchImpl-internal.hpp +5 -11
- data/vendor/datasketches-cpp/hll/include/HllSketchImpl.hpp +2 -4
- data/vendor/datasketches-cpp/hll/include/HllSketchImplFactory.hpp +11 -10
- data/vendor/datasketches-cpp/hll/include/HllUnion-internal.hpp +17 -8
- data/vendor/datasketches-cpp/hll/include/HllUtil.hpp +12 -3
- data/vendor/datasketches-cpp/hll/include/coupon_iterator-internal.hpp +2 -2
- data/vendor/datasketches-cpp/hll/include/coupon_iterator.hpp +3 -0
- data/vendor/datasketches-cpp/hll/include/hll.hpp +11 -4
- data/vendor/datasketches-cpp/hll/include/hll.private.hpp +19 -0
- data/vendor/datasketches-cpp/hll/test/CMakeLists.txt +2 -0
- data/vendor/datasketches-cpp/hll/test/CouponListTest.cpp +64 -0
- data/vendor/datasketches-cpp/hll/test/HllFullSizeTest.cpp +137 -0
- data/vendor/datasketches-cpp/hll/test/HllKxqRebuildTest.cpp +150 -0
- data/vendor/datasketches-cpp/hll/test/HllSketchTest.cpp +3 -3
- data/vendor/datasketches-cpp/hll/test/HllUnionTest.cpp +71 -1
- data/vendor/datasketches-cpp/kll/include/kll_helper_impl.hpp +10 -10
- data/vendor/datasketches-cpp/kll/include/kll_sketch.hpp +10 -1
- data/vendor/datasketches-cpp/kll/include/kll_sketch_impl.hpp +33 -24
- data/vendor/datasketches-cpp/kll/test/kll_sketch_deserialize_from_java_test.cpp +24 -0
- data/vendor/datasketches-cpp/kll/test/kll_sketch_serialize_for_java.cpp +10 -0
- data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch.hpp +11 -2
- data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch_impl.hpp +24 -15
- data/vendor/datasketches-cpp/req/include/req_sketch.hpp +9 -0
- data/vendor/datasketches-cpp/req/include/req_sketch_impl.hpp +24 -15
- data/vendor/datasketches-cpp/req/test/req_sketch_deserialize_from_java_test.cpp +46 -0
- data/vendor/datasketches-cpp/req/test/req_sketch_serialize_for_java.cpp +20 -0
- data/vendor/datasketches-cpp/req/test/req_sketch_test.cpp +70 -0
- data/vendor/datasketches-cpp/sampling/include/ebpps_sample_impl.hpp +17 -8
- data/vendor/datasketches-cpp/sampling/include/ebpps_sketch.hpp +13 -0
- data/vendor/datasketches-cpp/sampling/include/var_opt_sketch.hpp +10 -1
- data/vendor/datasketches-cpp/sampling/include/var_opt_sketch_impl.hpp +6 -7
- data/vendor/datasketches-cpp/sampling/include/var_opt_union.hpp +5 -1
- data/vendor/datasketches-cpp/sampling/include/var_opt_union_impl.hpp +2 -1
- data/vendor/datasketches-cpp/sampling/test/ebpps_allocation_test.cpp +1 -1
- data/vendor/datasketches-cpp/sampling/test/ebpps_sketch_test.cpp +2 -2
- data/vendor/datasketches-cpp/sampling/test/var_opt_allocation_test.cpp +1 -1
- data/vendor/datasketches-cpp/sampling/test/var_opt_sketch_test.cpp +10 -4
- data/vendor/datasketches-cpp/sampling/test/var_opt_union_test.cpp +12 -0
- data/vendor/datasketches-cpp/tdigest/include/tdigest.hpp +38 -2
- data/vendor/datasketches-cpp/tdigest/include/tdigest_impl.hpp +168 -9
- data/vendor/datasketches-cpp/tdigest/test/CMakeLists.txt +1 -0
- data/vendor/datasketches-cpp/tdigest/test/tdigest_iterator_test.cpp +274 -0
- data/vendor/datasketches-cpp/tdigest/test/tdigest_test.cpp +275 -0
- data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser.hpp +2 -0
- data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser_impl.hpp +24 -3
- data/vendor/datasketches-cpp/theta/include/theta_constants.hpp +4 -2
- data/vendor/datasketches-cpp/theta/include/theta_helpers.hpp +32 -0
- data/vendor/datasketches-cpp/theta/include/theta_set_difference_base_impl.hpp +4 -2
- data/vendor/datasketches-cpp/theta/include/theta_sketch.hpp +22 -4
- data/vendor/datasketches-cpp/theta/include/theta_sketch_impl.hpp +60 -38
- data/vendor/datasketches-cpp/theta/include/theta_union_base_impl.hpp +2 -6
- data/vendor/datasketches-cpp/theta/include/theta_update_sketch_base_impl.hpp +2 -2
- data/vendor/datasketches-cpp/theta/test/bit_packing_test.cpp +50 -0
- data/vendor/datasketches-cpp/theta/test/theta_a_not_b_test.cpp +22 -0
- data/vendor/datasketches-cpp/theta/test/theta_sketch_test.cpp +315 -0
- data/vendor/datasketches-cpp/tools/rat-check.sh +68 -0
- data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch.hpp +35 -4
- data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch_impl.hpp +2 -2
- data/vendor/datasketches-cpp/tuple/include/tuple_sketch.hpp +41 -0
- data/vendor/datasketches-cpp/tuple/include/tuple_sketch_impl.hpp +5 -4
- data/vendor/datasketches-cpp/tuple/test/tuple_sketch_test.cpp +59 -0
- data/vendor/datasketches-cpp/version.cfg.in +1 -1
- metadata +12 -2
|
@@ -23,12 +23,35 @@
|
|
|
23
23
|
#include <algorithm>
|
|
24
24
|
#include <cmath>
|
|
25
25
|
#include <sstream>
|
|
26
|
+
#include <type_traits>
|
|
26
27
|
|
|
27
28
|
#include "common_defs.hpp"
|
|
28
29
|
#include "memory_operations.hpp"
|
|
29
30
|
|
|
30
31
|
namespace datasketches {
|
|
31
32
|
|
|
33
|
+
template<typename T>
|
|
34
|
+
inline void check_not_nan(T value, const char* name) {
|
|
35
|
+
if (std::isnan(value)) {
|
|
36
|
+
throw std::invalid_argument(std::string(name) + " must not be NaN");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
template<typename T>
|
|
41
|
+
inline void check_not_infinite(T value, const char* name) {
|
|
42
|
+
if (std::isinf(value)) {
|
|
43
|
+
throw std::invalid_argument(std::string(name) + " must not be infinite");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
template<typename T>
|
|
48
|
+
inline void check_non_zero(T value, const char* name) {
|
|
49
|
+
static_assert(std::is_arithmetic<T>::value, "T must be an arithmetic type");
|
|
50
|
+
if (value == 0) {
|
|
51
|
+
throw std::invalid_argument(std::string(name) + " must not be zero");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
32
55
|
template<typename T, typename A>
|
|
33
56
|
tdigest<T, A>::tdigest(uint16_t k, const A& allocator):
|
|
34
57
|
tdigest(false, k, std::numeric_limits<T>::infinity(), -std::numeric_limits<T>::infinity(), vector_centroid(allocator), 0, vector_t(allocator))
|
|
@@ -37,6 +60,7 @@ tdigest(false, k, std::numeric_limits<T>::infinity(), -std::numeric_limits<T>::i
|
|
|
37
60
|
template<typename T, typename A>
|
|
38
61
|
void tdigest<T, A>::update(T value) {
|
|
39
62
|
if (std::isnan(value)) return;
|
|
63
|
+
if (std::isinf(value)) return;
|
|
40
64
|
if (buffer_.size() == centroids_capacity_ * BUFFER_MULTIPLIER) compress();
|
|
41
65
|
buffer_.push_back(value);
|
|
42
66
|
min_ = std::min(min_, value);
|
|
@@ -46,6 +70,9 @@ void tdigest<T, A>::update(T value) {
|
|
|
46
70
|
template<typename T, typename A>
|
|
47
71
|
void tdigest<T, A>::merge(const tdigest& other) {
|
|
48
72
|
if (other.is_empty()) return;
|
|
73
|
+
// Extreme centroid means are not the true min/max when those centroids have weight > 1.
|
|
74
|
+
min_ = std::min(min_, other.min_);
|
|
75
|
+
max_ = std::max(max_, other.max_);
|
|
49
76
|
vector_centroid tmp(buffer_.get_allocator());
|
|
50
77
|
tmp.reserve(buffer_.size() + centroids_.size() + other.buffer_.size() + other.centroids_.size());
|
|
51
78
|
for (const T value: buffer_) tmp.push_back(centroid(value, 1));
|
|
@@ -106,7 +133,7 @@ double tdigest<T, A>::get_rank(T value) const {
|
|
|
106
133
|
if (value < first_mean) {
|
|
107
134
|
if (first_mean - min_ > 0) {
|
|
108
135
|
if (value == min_) return 0.5 / centroids_weight_;
|
|
109
|
-
return (1.0 + (value - min_) / (first_mean - min_) * (centroids_.front().get_weight() / 2.0 - 1.0))
|
|
136
|
+
return (1.0 + (value - min_) / (first_mean - min_) * (centroids_.front().get_weight() / 2.0 - 1.0)) / centroids_weight_;
|
|
110
137
|
}
|
|
111
138
|
return 0; // should never happen
|
|
112
139
|
}
|
|
@@ -116,7 +143,7 @@ double tdigest<T, A>::get_rank(T value) const {
|
|
|
116
143
|
if (value > last_mean) {
|
|
117
144
|
if (max_ - last_mean > 0) {
|
|
118
145
|
if (value == max_) return 1.0 - 0.5 / centroids_weight_;
|
|
119
|
-
|
|
146
|
+
return 1.0 - ((1.0 + (max_ - value) / (max_ - last_mean) * (centroids_.back().get_weight() / 2.0 - 1.0)) / centroids_weight_);
|
|
120
147
|
}
|
|
121
148
|
return 1; // should never happen
|
|
122
149
|
}
|
|
@@ -166,7 +193,10 @@ T tdigest<T, A>::get_quantile(double rank) const {
|
|
|
166
193
|
}
|
|
167
194
|
const double last_weight = centroids_.back().get_weight();
|
|
168
195
|
if (last_weight > 1 && centroids_weight_ - weight <= last_weight / 2.0) {
|
|
169
|
-
|
|
196
|
+
// A last centroid of weight 2 makes the denominator zero. The only rank that
|
|
197
|
+
// reaches this branch is the one that returns the stored maximum.
|
|
198
|
+
if (last_weight == 2) return max_;
|
|
199
|
+
return max_ - (centroids_weight_ - weight - 1.0) / (last_weight / 2.0 - 1.0) * (max_ - centroids_.back().get_mean());
|
|
170
200
|
}
|
|
171
201
|
|
|
172
202
|
// interpolate between extremes
|
|
@@ -187,13 +217,15 @@ T tdigest<T, A>::get_quantile(double rank) const {
|
|
|
187
217
|
}
|
|
188
218
|
const double w1 = weight - weight_so_far - left_weight;
|
|
189
219
|
const double w2 = weight_so_far + dw - weight - right_weight;
|
|
190
|
-
|
|
220
|
+
// Weight each centroid by the distance to the other one, so the estimate
|
|
221
|
+
// moves toward the nearer centroid.
|
|
222
|
+
return weighted_average(centroids_[i].get_mean(), w2, centroids_[i + 1].get_mean(), w1);
|
|
191
223
|
}
|
|
192
224
|
weight_so_far += dw;
|
|
193
225
|
}
|
|
194
226
|
const double w1 = weight - centroids_weight_ - centroids_.back().get_weight() / 2.0;
|
|
195
227
|
const double w2 = centroids_.back().get_weight() / 2.0 - w1;
|
|
196
|
-
return weighted_average(centroids_.back().
|
|
228
|
+
return weighted_average(centroids_.back().get_mean(), w1, max_, w2);
|
|
197
229
|
}
|
|
198
230
|
|
|
199
231
|
template<typename T, typename A>
|
|
@@ -400,6 +432,8 @@ tdigest<T, A> tdigest<T, A>::deserialize(std::istream& is, const A& allocator) {
|
|
|
400
432
|
const bool reverse_merge = flags_byte & (1 << flags::REVERSE_MERGE);
|
|
401
433
|
if (is_single_value) {
|
|
402
434
|
const T value = read<T>(is);
|
|
435
|
+
check_not_nan(value, "single_value");
|
|
436
|
+
check_not_infinite(value, "single_value");
|
|
403
437
|
return tdigest(reverse_merge, k, value, value, vector_centroid(1, centroid(value, 1), allocator), 1, vector_t(allocator));
|
|
404
438
|
}
|
|
405
439
|
|
|
@@ -408,12 +442,26 @@ tdigest<T, A> tdigest<T, A>::deserialize(std::istream& is, const A& allocator) {
|
|
|
408
442
|
|
|
409
443
|
const T min = read<T>(is);
|
|
410
444
|
const T max = read<T>(is);
|
|
445
|
+
check_not_nan(min, "min");
|
|
446
|
+
check_not_infinite(min, "min");
|
|
447
|
+
check_not_nan(max, "max");
|
|
448
|
+
check_not_infinite(max, "max");
|
|
411
449
|
vector_centroid centroids(num_centroids, centroid(0, 0), allocator);
|
|
412
450
|
if (num_centroids > 0) read(is, centroids.data(), num_centroids * sizeof(centroid));
|
|
413
451
|
vector_t buffer(num_buffered, 0, allocator);
|
|
414
452
|
if (num_buffered > 0) read(is, buffer.data(), num_buffered * sizeof(T));
|
|
415
453
|
uint64_t weight = 0;
|
|
416
|
-
for (const auto& c: centroids)
|
|
454
|
+
for (const auto& c: centroids) {
|
|
455
|
+
check_not_nan(c.get_mean(), "centroid mean");
|
|
456
|
+
check_not_infinite(c.get_mean(), "centroid mean");
|
|
457
|
+
check_non_zero(c.get_weight(), "centroid weight");
|
|
458
|
+
|
|
459
|
+
weight += c.get_weight();
|
|
460
|
+
}
|
|
461
|
+
for (const auto& value: buffer) {
|
|
462
|
+
check_not_nan(value, "buffered_value");
|
|
463
|
+
check_not_infinite(value, "buffered_value");
|
|
464
|
+
}
|
|
417
465
|
return tdigest(reverse_merge, k, min, max, std::move(centroids), weight, std::move(buffer));
|
|
418
466
|
}
|
|
419
467
|
|
|
@@ -451,6 +499,8 @@ tdigest<T, A> tdigest<T, A>::deserialize(const void* bytes, size_t size, const A
|
|
|
451
499
|
ensure_minimum_memory(end_ptr - ptr, sizeof(T));
|
|
452
500
|
T value;
|
|
453
501
|
ptr += copy_from_mem(ptr, value);
|
|
502
|
+
check_not_nan(value, "single_value");
|
|
503
|
+
check_not_infinite(value, "single_value");
|
|
454
504
|
return tdigest(reverse_merge, k, value, value, vector_centroid(1, centroid(value, 1), allocator), 1, vector_t(allocator));
|
|
455
505
|
}
|
|
456
506
|
|
|
@@ -465,12 +515,26 @@ tdigest<T, A> tdigest<T, A>::deserialize(const void* bytes, size_t size, const A
|
|
|
465
515
|
ptr += copy_from_mem(ptr, min);
|
|
466
516
|
T max;
|
|
467
517
|
ptr += copy_from_mem(ptr, max);
|
|
518
|
+
check_not_nan(min, "min");
|
|
519
|
+
check_not_infinite(min, "min");
|
|
520
|
+
check_not_nan(max, "max");
|
|
521
|
+
check_not_infinite(max, "max");
|
|
468
522
|
vector_centroid centroids(num_centroids, centroid(0, 0), allocator);
|
|
469
523
|
if (num_centroids > 0) ptr += copy_from_mem(ptr, centroids.data(), num_centroids * sizeof(centroid));
|
|
470
524
|
vector_t buffer(num_buffered, 0, allocator);
|
|
471
525
|
if (num_buffered > 0) copy_from_mem(ptr, buffer.data(), num_buffered * sizeof(T));
|
|
472
526
|
uint64_t weight = 0;
|
|
473
|
-
for (const auto& c: centroids)
|
|
527
|
+
for (const auto& c: centroids) {
|
|
528
|
+
check_not_nan(c.get_mean(), "centroid mean");
|
|
529
|
+
check_not_infinite(c.get_mean(), "centroid mean");
|
|
530
|
+
check_non_zero(c.get_weight(), "centroid weight");
|
|
531
|
+
|
|
532
|
+
weight += c.get_weight();
|
|
533
|
+
}
|
|
534
|
+
for (const auto& value: buffer) {
|
|
535
|
+
check_not_nan(value, "buffered_value");
|
|
536
|
+
check_not_infinite(value, "buffered_value");
|
|
537
|
+
}
|
|
474
538
|
return tdigest(reverse_merge, k, min, max, std::move(centroids), weight, std::move(buffer));
|
|
475
539
|
}
|
|
476
540
|
|
|
@@ -487,13 +551,24 @@ tdigest<T, A> tdigest<T, A>::deserialize_compat(std::istream& is, const A& alloc
|
|
|
487
551
|
if (type == COMPAT_DOUBLE) { // compatibility with asBytes()
|
|
488
552
|
const auto min = read_big_endian<double>(is);
|
|
489
553
|
const auto max = read_big_endian<double>(is);
|
|
554
|
+
check_not_nan(min, "min");
|
|
555
|
+
check_not_infinite(min, "min");
|
|
556
|
+
check_not_nan(max, "max");
|
|
557
|
+
check_not_infinite(max, "max");
|
|
490
558
|
const auto k = static_cast<uint16_t>(read_big_endian<double>(is));
|
|
491
559
|
const auto num_centroids = read_big_endian<uint32_t>(is);
|
|
492
560
|
vector_centroid centroids(num_centroids, centroid(0, 0), allocator);
|
|
493
561
|
uint64_t total_weight = 0;
|
|
494
562
|
for (auto& c: centroids) {
|
|
495
|
-
const
|
|
563
|
+
const auto weight_double = read_big_endian<double>(is);
|
|
564
|
+
check_not_nan(weight_double, "centroid weight");
|
|
565
|
+
check_not_infinite(weight_double, "centroid weight");
|
|
566
|
+
check_non_zero(weight_double, "centroid weight");
|
|
567
|
+
|
|
496
568
|
const auto mean = read_big_endian<double>(is);
|
|
569
|
+
check_not_nan(mean, "centroid mean");
|
|
570
|
+
check_not_infinite(mean, "centroid mean");
|
|
571
|
+
const W weight = static_cast<W>(weight_double);
|
|
497
572
|
c = centroid(mean, weight);
|
|
498
573
|
total_weight += weight;
|
|
499
574
|
}
|
|
@@ -502,6 +577,10 @@ tdigest<T, A> tdigest<T, A>::deserialize_compat(std::istream& is, const A& alloc
|
|
|
502
577
|
// COMPAT_FLOAT: compatibility with asSmallBytes()
|
|
503
578
|
const auto min = read_big_endian<double>(is); // reference implementation uses doubles for min and max
|
|
504
579
|
const auto max = read_big_endian<double>(is);
|
|
580
|
+
check_not_nan(min, "min");
|
|
581
|
+
check_not_infinite(min, "min");
|
|
582
|
+
check_not_nan(max, "max");
|
|
583
|
+
check_not_infinite(max, "max");
|
|
505
584
|
const auto k = static_cast<uint16_t>(read_big_endian<float>(is));
|
|
506
585
|
// reference implementation stores capacities of the array of centroids and the buffer as shorts
|
|
507
586
|
// they can be derived from k in the constructor
|
|
@@ -510,8 +589,13 @@ tdigest<T, A> tdigest<T, A>::deserialize_compat(std::istream& is, const A& alloc
|
|
|
510
589
|
vector_centroid centroids(num_centroids, centroid(0, 0), allocator);
|
|
511
590
|
uint64_t total_weight = 0;
|
|
512
591
|
for (auto& c: centroids) {
|
|
513
|
-
const
|
|
592
|
+
const auto weight_float = read_big_endian<float>(is);
|
|
593
|
+
check_not_nan(weight_float, "centroid weight");
|
|
594
|
+
check_not_infinite(weight_float, "centroid weight");
|
|
514
595
|
const auto mean = read_big_endian<float>(is);
|
|
596
|
+
check_not_nan(mean, "centroid mean");
|
|
597
|
+
check_not_infinite(mean, "centroid mean");
|
|
598
|
+
const W weight = static_cast<W>(weight_float);
|
|
515
599
|
c = centroid(mean, weight);
|
|
516
600
|
total_weight += weight;
|
|
517
601
|
}
|
|
@@ -538,6 +622,10 @@ tdigest<T, A> tdigest<T, A>::deserialize_compat(const void* bytes, size_t size,
|
|
|
538
622
|
double max;
|
|
539
623
|
ptr += copy_from_mem(ptr, max);
|
|
540
624
|
max = byteswap(max);
|
|
625
|
+
check_not_nan(min, "min");
|
|
626
|
+
check_not_infinite(min, "min");
|
|
627
|
+
check_not_nan(max, "max");
|
|
628
|
+
check_not_infinite(max, "max");
|
|
541
629
|
double k_double;
|
|
542
630
|
ptr += copy_from_mem(ptr, k_double);
|
|
543
631
|
const uint16_t k = static_cast<uint16_t>(byteswap(k_double));
|
|
@@ -554,6 +642,10 @@ tdigest<T, A> tdigest<T, A>::deserialize_compat(const void* bytes, size_t size,
|
|
|
554
642
|
double mean;
|
|
555
643
|
ptr += copy_from_mem(ptr, mean);
|
|
556
644
|
mean = byteswap(mean);
|
|
645
|
+
check_not_nan(weight, "centroid weight");
|
|
646
|
+
check_not_infinite(weight, "centroid weight");
|
|
647
|
+
check_not_nan(mean, "centroid mean");
|
|
648
|
+
check_not_infinite(mean, "centroid mean");
|
|
557
649
|
c = centroid(mean, static_cast<W>(weight));
|
|
558
650
|
total_weight += static_cast<uint64_t>(weight);
|
|
559
651
|
}
|
|
@@ -567,6 +659,10 @@ tdigest<T, A> tdigest<T, A>::deserialize_compat(const void* bytes, size_t size,
|
|
|
567
659
|
double max;
|
|
568
660
|
ptr += copy_from_mem(ptr, max);
|
|
569
661
|
max = byteswap(max);
|
|
662
|
+
check_not_nan(min, "min");
|
|
663
|
+
check_not_infinite(min, "min");
|
|
664
|
+
check_not_nan(max, "max");
|
|
665
|
+
check_not_infinite(max, "max");
|
|
570
666
|
float k_float;
|
|
571
667
|
ptr += copy_from_mem(ptr, k_float);
|
|
572
668
|
const uint16_t k = static_cast<uint16_t>(byteswap(k_float));
|
|
@@ -586,6 +682,10 @@ tdigest<T, A> tdigest<T, A>::deserialize_compat(const void* bytes, size_t size,
|
|
|
586
682
|
float mean;
|
|
587
683
|
ptr += copy_from_mem(ptr, mean);
|
|
588
684
|
mean = byteswap(mean);
|
|
685
|
+
check_not_nan(weight, "centroid weight");
|
|
686
|
+
check_not_infinite(weight, "centroid weight");
|
|
687
|
+
check_not_nan(mean, "centroid mean");
|
|
688
|
+
check_not_infinite(mean, "centroid mean");
|
|
589
689
|
c = centroid(mean, static_cast<W>(weight));
|
|
590
690
|
total_weight += static_cast<uint64_t>(weight);
|
|
591
691
|
}
|
|
@@ -627,6 +727,65 @@ void tdigest<T, A>::check_split_points(const T* values, uint32_t size) {
|
|
|
627
727
|
}
|
|
628
728
|
}
|
|
629
729
|
|
|
730
|
+
template <typename T, typename A>
|
|
731
|
+
typename tdigest<T, A>::const_iterator tdigest<T, A>::begin() const {
|
|
732
|
+
return tdigest<T, A>::const_iterator(*this, false);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
template <typename T, typename A>
|
|
736
|
+
typename tdigest<T, A>::const_iterator tdigest<T, A>::end() const {
|
|
737
|
+
return tdigest::const_iterator(*this, true);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
template<typename T, typename A>
|
|
741
|
+
tdigest<T, A>::const_iterator::const_iterator(const tdigest& tdigest_, const bool is_end):
|
|
742
|
+
centroids_(tdigest_.get_allocator())
|
|
743
|
+
{
|
|
744
|
+
// Create a copy of the tdigest to generate the centroids after processing the buffered values
|
|
745
|
+
tdigest tmp(tdigest_);
|
|
746
|
+
tmp.compress();
|
|
747
|
+
centroids_.insert(centroids_.end(), tmp.centroids_.begin(), tmp.centroids_.end());
|
|
748
|
+
|
|
749
|
+
if (is_end) {
|
|
750
|
+
index_ = centroids_.size();
|
|
751
|
+
} else {
|
|
752
|
+
index_ = 0;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
template<typename T, typename A>
|
|
757
|
+
typename tdigest<T, A>::const_iterator& tdigest<T, A>::const_iterator::operator++() {
|
|
758
|
+
++index_;
|
|
759
|
+
return *this;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
template<typename T, typename A>
|
|
763
|
+
typename tdigest<T, A>::const_iterator& tdigest<T, A>::const_iterator::operator++(int) {
|
|
764
|
+
const_iterator tmp(*this);
|
|
765
|
+
operator++();
|
|
766
|
+
return tmp;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
template<typename T, typename A>
|
|
770
|
+
bool tdigest<T, A>::const_iterator::operator==(const const_iterator& other) const {
|
|
771
|
+
return index_ == other.index_;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
template<typename T, typename A>
|
|
775
|
+
bool tdigest<T, A>::const_iterator::operator!=(const const_iterator& other) const {
|
|
776
|
+
return !operator==(other);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
template<typename T, typename A>
|
|
780
|
+
auto tdigest<T, A>::const_iterator::operator*() const -> reference {
|
|
781
|
+
return value_type(centroids_[index_].get_mean(), centroids_[index_].get_weight());
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
template<typename T, typename A>
|
|
785
|
+
auto tdigest<T, A>::const_iterator::operator->() const -> pointer {
|
|
786
|
+
return **this;
|
|
787
|
+
}
|
|
788
|
+
|
|
630
789
|
} /* namespace datasketches */
|
|
631
790
|
|
|
632
791
|
#endif // _TDIGEST_IMPL_HPP_
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
#include <catch2/catch.hpp>
|
|
21
|
+
#include <memory>
|
|
22
|
+
#include <map>
|
|
23
|
+
#include <vector>
|
|
24
|
+
#include <set>
|
|
25
|
+
|
|
26
|
+
#include "tdigest.hpp"
|
|
27
|
+
|
|
28
|
+
namespace datasketches {
|
|
29
|
+
|
|
30
|
+
TEST_CASE("tdigest iterator: basic iteration", "[tdigest]") {
|
|
31
|
+
tdigest_double td(100);
|
|
32
|
+
|
|
33
|
+
// Insert 10 distinct values
|
|
34
|
+
for (int i = 0; i < 10; i++) {
|
|
35
|
+
td.update(static_cast<double>(i));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Collect all centroids via iteration
|
|
39
|
+
std::map<double, uint64_t> centroids;
|
|
40
|
+
for (const auto&& centroid : td) {
|
|
41
|
+
centroids[centroid.first] = centroid.second;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Should have collected all 10 distinct values
|
|
45
|
+
REQUIRE(centroids.size() == 10);
|
|
46
|
+
|
|
47
|
+
// Verify each value was captured correctly
|
|
48
|
+
for (int i = 0; i < 10; i++) {
|
|
49
|
+
REQUIRE(centroids.count(static_cast<double>(i)) == 1);
|
|
50
|
+
REQUIRE(centroids[static_cast<double>(i)] == 1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
TEST_CASE("tdigest iterator: explicit begin/end with unique_ptr", "[tdigest]") {
|
|
55
|
+
// This test reproduces the bug scenario found in ClickHouse
|
|
56
|
+
std::unique_ptr<tdigest_double> td(new tdigest_double(100));
|
|
57
|
+
|
|
58
|
+
// Insert distinct values
|
|
59
|
+
for (int i = 0; i < 10; i++) {
|
|
60
|
+
td->update(static_cast<double>(i));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Use explicit begin/end iterators
|
|
64
|
+
auto it = td->begin();
|
|
65
|
+
auto end_it = td->end();
|
|
66
|
+
|
|
67
|
+
std::vector<double> means;
|
|
68
|
+
std::vector<uint64_t> weights;
|
|
69
|
+
|
|
70
|
+
while (it != end_it) {
|
|
71
|
+
// Before the fix, accessing it->first would return garbage or same value repeatedly
|
|
72
|
+
double mean = it->first;
|
|
73
|
+
uint64_t weight = it->second;
|
|
74
|
+
means.push_back(mean);
|
|
75
|
+
weights.push_back(weight);
|
|
76
|
+
++it;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Should have collected 10 centroids
|
|
80
|
+
REQUIRE(means.size() == 10);
|
|
81
|
+
REQUIRE(weights.size() == 10);
|
|
82
|
+
|
|
83
|
+
// All means should be distinct (not all zeros or garbage)
|
|
84
|
+
std::set<double> unique_means(means.begin(), means.end());
|
|
85
|
+
REQUIRE(unique_means.size() == 10);
|
|
86
|
+
|
|
87
|
+
// Verify all expected values are present
|
|
88
|
+
for (int i = 0; i < 10; i++) {
|
|
89
|
+
REQUIRE(unique_means.count(static_cast<double>(i)) == 1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
TEST_CASE("tdigest iterator: structured bindings", "[tdigest]") {
|
|
94
|
+
tdigest_double td(100);
|
|
95
|
+
|
|
96
|
+
for (int i = 0; i < 5; i++) {
|
|
97
|
+
td.update(static_cast<double>(i * 10));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
std::vector<std::pair<double, uint64_t>> collected;
|
|
101
|
+
|
|
102
|
+
// Test structured bindings
|
|
103
|
+
for (auto it = td.begin(); it != td.end(); ++it) {
|
|
104
|
+
const auto& centroid = *it;
|
|
105
|
+
collected.emplace_back(centroid.first, centroid.second);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
REQUIRE(collected.size() == 5);
|
|
109
|
+
|
|
110
|
+
// Verify distinct values were collected
|
|
111
|
+
std::set<double> means;
|
|
112
|
+
for (const auto& pair : collected) {
|
|
113
|
+
means.insert(pair.first);
|
|
114
|
+
REQUIRE(pair.second == 1); // Each value inserted once
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
REQUIRE(means.size() == 5);
|
|
118
|
+
for (int i = 0; i < 5; i++) {
|
|
119
|
+
REQUIRE(means.count(static_cast<double>(i * 10)) == 1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
TEST_CASE("tdigest iterator: operator-> access", "[tdigest]") {
|
|
124
|
+
tdigest_double td(100);
|
|
125
|
+
|
|
126
|
+
// Insert values
|
|
127
|
+
for (int i = 1; i <= 10; i++) {
|
|
128
|
+
td.update(static_cast<double>(i * i)); // 1, 4, 9, 16, 25, 36, 49, 64, 81, 100
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Access via operator->
|
|
132
|
+
std::map<double, uint64_t> centroids;
|
|
133
|
+
auto end_it = td.end();
|
|
134
|
+
for (auto it = td.begin(); it != end_it; ++it) {
|
|
135
|
+
// operator-> should return valid values
|
|
136
|
+
centroids[it->first] = it->second;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
REQUIRE(centroids.size() == 10);
|
|
140
|
+
|
|
141
|
+
// Verify the squared values
|
|
142
|
+
for (int i = 1; i <= 10; i++) {
|
|
143
|
+
double expected = static_cast<double>(i * i);
|
|
144
|
+
REQUIRE(centroids.count(expected) == 1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
TEST_CASE("tdigest iterator: range-based for with const auto&&", "[tdigest]") {
|
|
149
|
+
tdigest_double td(100);
|
|
150
|
+
|
|
151
|
+
// Insert values
|
|
152
|
+
for (double d = 0.0; d < 10.0; d += 1.0) {
|
|
153
|
+
td.update(d);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
size_t count = 0;
|
|
157
|
+
std::set<double> seen_means;
|
|
158
|
+
|
|
159
|
+
// This pattern was working in simple tests but failing in optimized builds
|
|
160
|
+
for (const auto&& centroid : td) {
|
|
161
|
+
seen_means.insert(centroid.first);
|
|
162
|
+
count++;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
REQUIRE(count == 10);
|
|
166
|
+
REQUIRE(seen_means.size() == 10);
|
|
167
|
+
|
|
168
|
+
// Verify all values from 0 to 9 are present
|
|
169
|
+
for (int i = 0; i < 10; i++) {
|
|
170
|
+
REQUIRE(seen_means.count(static_cast<double>(i)) == 1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
TEST_CASE("tdigest iterator: copy vs reference semantics", "[tdigest]") {
|
|
175
|
+
tdigest_double td(100);
|
|
176
|
+
|
|
177
|
+
td.update(1.0);
|
|
178
|
+
td.update(2.0);
|
|
179
|
+
td.update(3.0);
|
|
180
|
+
|
|
181
|
+
auto it = td.begin();
|
|
182
|
+
|
|
183
|
+
// Store the pair
|
|
184
|
+
auto pair1 = *it;
|
|
185
|
+
double mean1 = pair1.first;
|
|
186
|
+
|
|
187
|
+
++it;
|
|
188
|
+
|
|
189
|
+
// Store another pair
|
|
190
|
+
auto pair2 = *it;
|
|
191
|
+
double mean2 = pair2.first;
|
|
192
|
+
|
|
193
|
+
++it;
|
|
194
|
+
|
|
195
|
+
auto pair3 = *it;
|
|
196
|
+
double mean3 = pair3.first;
|
|
197
|
+
|
|
198
|
+
// All three means should be distinct
|
|
199
|
+
REQUIRE(mean1 != mean2);
|
|
200
|
+
REQUIRE(mean2 != mean3);
|
|
201
|
+
REQUIRE(mean1 != mean3);
|
|
202
|
+
|
|
203
|
+
// And they should match our input values
|
|
204
|
+
std::set<double> means = {mean1, mean2, mean3};
|
|
205
|
+
REQUIRE(means.count(1.0) == 1);
|
|
206
|
+
REQUIRE(means.count(2.0) == 1);
|
|
207
|
+
REQUIRE(means.count(3.0) == 1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
TEST_CASE("tdigest iterator: empty sketch", "[tdigest]") {
|
|
211
|
+
tdigest_double td(100);
|
|
212
|
+
|
|
213
|
+
// Empty sketch should have begin() == end()
|
|
214
|
+
REQUIRE(td.begin() == td.end());
|
|
215
|
+
|
|
216
|
+
// Range-based for should not execute
|
|
217
|
+
size_t count = 0;
|
|
218
|
+
for (const auto&& centroid : td) {
|
|
219
|
+
(void)centroid; // Silence unused warning
|
|
220
|
+
count++;
|
|
221
|
+
}
|
|
222
|
+
REQUIRE(count == 0);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
TEST_CASE("tdigest iterator: single value", "[tdigest]") {
|
|
226
|
+
tdigest_double td(100);
|
|
227
|
+
td.update(42.0);
|
|
228
|
+
|
|
229
|
+
size_t count = 0;
|
|
230
|
+
double captured_mean = 0.0;
|
|
231
|
+
uint64_t captured_weight = 0;
|
|
232
|
+
|
|
233
|
+
for (const auto&& centroid : td) {
|
|
234
|
+
captured_mean = centroid.first;
|
|
235
|
+
captured_weight = centroid.second;
|
|
236
|
+
count++;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
REQUIRE(count == 1);
|
|
240
|
+
REQUIRE(captured_mean == 42.0);
|
|
241
|
+
REQUIRE(captured_weight == 1);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
TEST_CASE("tdigest iterator: large dataset", "[tdigest]") {
|
|
245
|
+
tdigest_double td(100);
|
|
246
|
+
|
|
247
|
+
// Insert 1000 distinct values
|
|
248
|
+
for (int i = 0; i < 1000; i++) {
|
|
249
|
+
td.update(static_cast<double>(i));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Iterator should provide compressed centroids (not all 1000)
|
|
253
|
+
size_t centroid_count = 0;
|
|
254
|
+
std::set<double> unique_means;
|
|
255
|
+
uint64_t total_weight = 0;
|
|
256
|
+
|
|
257
|
+
for (const auto&& centroid : td) {
|
|
258
|
+
unique_means.insert(centroid.first);
|
|
259
|
+
total_weight += centroid.second;
|
|
260
|
+
centroid_count++;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Should have fewer centroids than input values due to compression
|
|
264
|
+
REQUIRE(centroid_count < 1000);
|
|
265
|
+
REQUIRE(centroid_count > 0);
|
|
266
|
+
|
|
267
|
+
// Total weight should equal number of input values
|
|
268
|
+
REQUIRE(total_weight == 1000);
|
|
269
|
+
|
|
270
|
+
// All means should be unique (no duplicates)
|
|
271
|
+
REQUIRE(unique_means.size() == centroid_count);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
} // namespace datasketches
|