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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/ext/datasketches/cpc_wrapper.cpp +8 -9
  4. data/ext/datasketches/fi_wrapper.cpp +4 -5
  5. data/ext/datasketches/hll_wrapper.cpp +9 -10
  6. data/ext/datasketches/kll_wrapper.cpp +13 -12
  7. data/ext/datasketches/theta_wrapper.cpp +11 -13
  8. data/ext/datasketches/vo_wrapper.cpp +1 -2
  9. data/lib/datasketches/version.rb +1 -1
  10. data/vendor/datasketches-cpp/CMakeLists.txt +5 -3
  11. data/vendor/datasketches-cpp/CODE_OF_CONDUCT.md +1 -1
  12. data/vendor/datasketches-cpp/LICENSE +14 -0
  13. data/vendor/datasketches-cpp/README.md +67 -73
  14. data/vendor/datasketches-cpp/benchmarks/CMakeLists.txt +52 -0
  15. data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch.cpp +153 -0
  16. data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch_serialization.cpp +161 -0
  17. data/vendor/datasketches-cpp/common/CMakeLists.txt +1 -0
  18. data/vendor/datasketches-cpp/common/include/binomial_bounds.hpp +2 -2
  19. data/vendor/datasketches-cpp/common/include/fdlibm_log.hpp +101 -0
  20. data/vendor/datasketches-cpp/common/include/serde.hpp +6 -0
  21. data/vendor/datasketches-cpp/common/test/CMakeLists.txt +44 -2
  22. data/vendor/datasketches-cpp/common/test/binomial_bounds_test.cpp +279 -0
  23. data/vendor/datasketches-cpp/common/test/deserialize_hardening_test.cpp +188 -0
  24. data/vendor/datasketches-cpp/count/include/count_min.hpp +17 -4
  25. data/vendor/datasketches-cpp/count/include/count_min_impl.hpp +65 -83
  26. data/vendor/datasketches-cpp/count/test/count_min_test.cpp +63 -7
  27. data/vendor/datasketches-cpp/cpc/include/compression_data.hpp +2 -0
  28. data/vendor/datasketches-cpp/cpc/include/cpc_compressor_impl.hpp +16 -7
  29. data/vendor/datasketches-cpp/cpc/include/cpc_sketch.hpp +1 -0
  30. data/vendor/datasketches-cpp/cpc/include/cpc_sketch_impl.hpp +54 -36
  31. data/vendor/datasketches-cpp/cpc/include/cpc_union_impl.hpp +27 -27
  32. data/vendor/datasketches-cpp/cpc/include/cpc_util.hpp +7 -7
  33. data/vendor/datasketches-cpp/cpc/include/icon_estimator.hpp +5 -5
  34. data/vendor/datasketches-cpp/cpc/include/u32_table_impl.hpp +16 -16
  35. data/vendor/datasketches-cpp/cpc/test/cpc_sketch_test.cpp +35 -0
  36. data/vendor/datasketches-cpp/fi/include/frequent_items_sketch.hpp +28 -3
  37. data/vendor/datasketches-cpp/fi/include/frequent_items_sketch_impl.hpp +41 -25
  38. data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map.hpp +3 -1
  39. data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map_impl.hpp +10 -5
  40. data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_serialize_for_java.cpp +24 -0
  41. data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_test.cpp +116 -0
  42. data/vendor/datasketches-cpp/filters/include/bloom_filter.hpp +1 -1
  43. data/vendor/datasketches-cpp/filters/include/bloom_filter_impl.hpp +32 -12
  44. data/vendor/datasketches-cpp/filters/test/bloom_filter_test.cpp +28 -1
  45. data/vendor/datasketches-cpp/hll/include/CouponHashSet-internal.hpp +1 -2
  46. data/vendor/datasketches-cpp/hll/include/CouponList-internal.hpp +19 -5
  47. data/vendor/datasketches-cpp/hll/include/CubicInterpolation-internal.hpp +4 -4
  48. data/vendor/datasketches-cpp/hll/include/HarmonicNumbers-internal.hpp +2 -1
  49. data/vendor/datasketches-cpp/hll/include/Hll4Array-internal.hpp +4 -4
  50. data/vendor/datasketches-cpp/hll/include/Hll4Array.hpp +1 -1
  51. data/vendor/datasketches-cpp/hll/include/Hll6Array-internal.hpp +3 -3
  52. data/vendor/datasketches-cpp/hll/include/Hll6Array.hpp +1 -1
  53. data/vendor/datasketches-cpp/hll/include/Hll8Array-internal.hpp +6 -3
  54. data/vendor/datasketches-cpp/hll/include/Hll8Array.hpp +1 -1
  55. data/vendor/datasketches-cpp/hll/include/HllArray-internal.hpp +35 -29
  56. data/vendor/datasketches-cpp/hll/include/HllArray.hpp +1 -1
  57. data/vendor/datasketches-cpp/hll/include/HllSketch-internal.hpp +3 -5
  58. data/vendor/datasketches-cpp/hll/include/HllSketchImpl-internal.hpp +5 -11
  59. data/vendor/datasketches-cpp/hll/include/HllSketchImpl.hpp +2 -4
  60. data/vendor/datasketches-cpp/hll/include/HllSketchImplFactory.hpp +11 -10
  61. data/vendor/datasketches-cpp/hll/include/HllUnion-internal.hpp +17 -8
  62. data/vendor/datasketches-cpp/hll/include/HllUtil.hpp +12 -3
  63. data/vendor/datasketches-cpp/hll/include/coupon_iterator-internal.hpp +2 -2
  64. data/vendor/datasketches-cpp/hll/include/coupon_iterator.hpp +3 -0
  65. data/vendor/datasketches-cpp/hll/include/hll.hpp +11 -4
  66. data/vendor/datasketches-cpp/hll/include/hll.private.hpp +19 -0
  67. data/vendor/datasketches-cpp/hll/test/CMakeLists.txt +2 -0
  68. data/vendor/datasketches-cpp/hll/test/CouponListTest.cpp +64 -0
  69. data/vendor/datasketches-cpp/hll/test/HllFullSizeTest.cpp +137 -0
  70. data/vendor/datasketches-cpp/hll/test/HllKxqRebuildTest.cpp +150 -0
  71. data/vendor/datasketches-cpp/hll/test/HllSketchTest.cpp +3 -3
  72. data/vendor/datasketches-cpp/hll/test/HllUnionTest.cpp +71 -1
  73. data/vendor/datasketches-cpp/kll/include/kll_helper_impl.hpp +10 -10
  74. data/vendor/datasketches-cpp/kll/include/kll_sketch.hpp +10 -1
  75. data/vendor/datasketches-cpp/kll/include/kll_sketch_impl.hpp +33 -24
  76. data/vendor/datasketches-cpp/kll/test/kll_sketch_deserialize_from_java_test.cpp +24 -0
  77. data/vendor/datasketches-cpp/kll/test/kll_sketch_serialize_for_java.cpp +10 -0
  78. data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch.hpp +11 -2
  79. data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch_impl.hpp +24 -15
  80. data/vendor/datasketches-cpp/req/include/req_sketch.hpp +9 -0
  81. data/vendor/datasketches-cpp/req/include/req_sketch_impl.hpp +24 -15
  82. data/vendor/datasketches-cpp/req/test/req_sketch_deserialize_from_java_test.cpp +46 -0
  83. data/vendor/datasketches-cpp/req/test/req_sketch_serialize_for_java.cpp +20 -0
  84. data/vendor/datasketches-cpp/req/test/req_sketch_test.cpp +70 -0
  85. data/vendor/datasketches-cpp/sampling/include/ebpps_sample_impl.hpp +17 -8
  86. data/vendor/datasketches-cpp/sampling/include/ebpps_sketch.hpp +13 -0
  87. data/vendor/datasketches-cpp/sampling/include/var_opt_sketch.hpp +10 -1
  88. data/vendor/datasketches-cpp/sampling/include/var_opt_sketch_impl.hpp +6 -7
  89. data/vendor/datasketches-cpp/sampling/include/var_opt_union.hpp +5 -1
  90. data/vendor/datasketches-cpp/sampling/include/var_opt_union_impl.hpp +2 -1
  91. data/vendor/datasketches-cpp/sampling/test/ebpps_allocation_test.cpp +1 -1
  92. data/vendor/datasketches-cpp/sampling/test/ebpps_sketch_test.cpp +2 -2
  93. data/vendor/datasketches-cpp/sampling/test/var_opt_allocation_test.cpp +1 -1
  94. data/vendor/datasketches-cpp/sampling/test/var_opt_sketch_test.cpp +10 -4
  95. data/vendor/datasketches-cpp/sampling/test/var_opt_union_test.cpp +12 -0
  96. data/vendor/datasketches-cpp/tdigest/include/tdigest.hpp +38 -2
  97. data/vendor/datasketches-cpp/tdigest/include/tdigest_impl.hpp +168 -9
  98. data/vendor/datasketches-cpp/tdigest/test/CMakeLists.txt +1 -0
  99. data/vendor/datasketches-cpp/tdigest/test/tdigest_iterator_test.cpp +274 -0
  100. data/vendor/datasketches-cpp/tdigest/test/tdigest_test.cpp +275 -0
  101. data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser.hpp +2 -0
  102. data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser_impl.hpp +24 -3
  103. data/vendor/datasketches-cpp/theta/include/theta_constants.hpp +4 -2
  104. data/vendor/datasketches-cpp/theta/include/theta_helpers.hpp +32 -0
  105. data/vendor/datasketches-cpp/theta/include/theta_set_difference_base_impl.hpp +4 -2
  106. data/vendor/datasketches-cpp/theta/include/theta_sketch.hpp +22 -4
  107. data/vendor/datasketches-cpp/theta/include/theta_sketch_impl.hpp +60 -38
  108. data/vendor/datasketches-cpp/theta/include/theta_union_base_impl.hpp +2 -6
  109. data/vendor/datasketches-cpp/theta/include/theta_update_sketch_base_impl.hpp +2 -2
  110. data/vendor/datasketches-cpp/theta/test/bit_packing_test.cpp +50 -0
  111. data/vendor/datasketches-cpp/theta/test/theta_a_not_b_test.cpp +22 -0
  112. data/vendor/datasketches-cpp/theta/test/theta_sketch_test.cpp +315 -0
  113. data/vendor/datasketches-cpp/tools/rat-check.sh +68 -0
  114. data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch.hpp +35 -4
  115. data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch_impl.hpp +2 -2
  116. data/vendor/datasketches-cpp/tuple/include/tuple_sketch.hpp +41 -0
  117. data/vendor/datasketches-cpp/tuple/include/tuple_sketch_impl.hpp +5 -4
  118. data/vendor/datasketches-cpp/tuple/test/tuple_sketch_test.cpp +59 -0
  119. data/vendor/datasketches-cpp/version.cfg.in +1 -1
  120. 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
- return 1.0 - ((1.0 + (max_ - value) / (max_ - last_mean) * (centroids_.back().get_weight() / 2.0 - 1.0)) / centroids_weight_); // ?
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
- return max_ + (centroids_weight_ - weight - 1.0) / (last_weight / 2.0 - 1.0) * (max_ - centroids_.back().get_mean());
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
- return weighted_average(centroids_[i].get_mean(), w1, centroids_[i + 1].get_mean(), w2);
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().get_weight(), w1, max_, w2);
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) weight += c.get_weight();
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) weight += c.get_weight();
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 W weight = static_cast<W>(read_big_endian<double>(is));
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 W weight = static_cast<W>(read_big_endian<float>(is));
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_
@@ -39,6 +39,7 @@ target_sources(tdigest_test
39
39
  PRIVATE
40
40
  tdigest_test.cpp
41
41
  tdigest_custom_allocator_test.cpp
42
+ tdigest_iterator_test.cpp
42
43
  )
43
44
 
44
45
  if (SERDE_COMPAT)
@@ -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