datasketches 0.5.2 → 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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/ext/datasketches/theta_wrapper.cpp +1 -1
  4. data/lib/datasketches/version.rb +1 -1
  5. data/vendor/datasketches-cpp/CMakeLists.txt +5 -3
  6. data/vendor/datasketches-cpp/CODE_OF_CONDUCT.md +1 -1
  7. data/vendor/datasketches-cpp/LICENSE +14 -0
  8. data/vendor/datasketches-cpp/README.md +67 -73
  9. data/vendor/datasketches-cpp/benchmarks/CMakeLists.txt +52 -0
  10. data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch.cpp +153 -0
  11. data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch_serialization.cpp +161 -0
  12. data/vendor/datasketches-cpp/common/CMakeLists.txt +1 -0
  13. data/vendor/datasketches-cpp/common/include/binomial_bounds.hpp +2 -2
  14. data/vendor/datasketches-cpp/common/include/fdlibm_log.hpp +101 -0
  15. data/vendor/datasketches-cpp/common/include/serde.hpp +6 -0
  16. data/vendor/datasketches-cpp/common/test/CMakeLists.txt +44 -2
  17. data/vendor/datasketches-cpp/common/test/binomial_bounds_test.cpp +279 -0
  18. data/vendor/datasketches-cpp/common/test/deserialize_hardening_test.cpp +188 -0
  19. data/vendor/datasketches-cpp/count/include/count_min.hpp +17 -4
  20. data/vendor/datasketches-cpp/count/include/count_min_impl.hpp +65 -83
  21. data/vendor/datasketches-cpp/count/test/count_min_test.cpp +63 -7
  22. data/vendor/datasketches-cpp/cpc/include/compression_data.hpp +2 -0
  23. data/vendor/datasketches-cpp/cpc/include/cpc_compressor_impl.hpp +16 -7
  24. data/vendor/datasketches-cpp/cpc/include/cpc_sketch.hpp +1 -0
  25. data/vendor/datasketches-cpp/cpc/include/cpc_sketch_impl.hpp +54 -36
  26. data/vendor/datasketches-cpp/cpc/include/cpc_union_impl.hpp +27 -27
  27. data/vendor/datasketches-cpp/cpc/include/cpc_util.hpp +7 -7
  28. data/vendor/datasketches-cpp/cpc/include/icon_estimator.hpp +5 -5
  29. data/vendor/datasketches-cpp/cpc/include/u32_table_impl.hpp +16 -16
  30. data/vendor/datasketches-cpp/cpc/test/cpc_sketch_test.cpp +35 -0
  31. data/vendor/datasketches-cpp/fi/include/frequent_items_sketch.hpp +28 -3
  32. data/vendor/datasketches-cpp/fi/include/frequent_items_sketch_impl.hpp +41 -25
  33. data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map.hpp +3 -1
  34. data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map_impl.hpp +10 -5
  35. data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_serialize_for_java.cpp +24 -0
  36. data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_test.cpp +116 -0
  37. data/vendor/datasketches-cpp/filters/include/bloom_filter.hpp +1 -1
  38. data/vendor/datasketches-cpp/filters/include/bloom_filter_impl.hpp +32 -12
  39. data/vendor/datasketches-cpp/filters/test/bloom_filter_test.cpp +28 -1
  40. data/vendor/datasketches-cpp/hll/include/CouponHashSet-internal.hpp +1 -2
  41. data/vendor/datasketches-cpp/hll/include/CouponList-internal.hpp +19 -5
  42. data/vendor/datasketches-cpp/hll/include/CubicInterpolation-internal.hpp +4 -4
  43. data/vendor/datasketches-cpp/hll/include/HarmonicNumbers-internal.hpp +2 -1
  44. data/vendor/datasketches-cpp/hll/include/Hll4Array-internal.hpp +4 -4
  45. data/vendor/datasketches-cpp/hll/include/Hll4Array.hpp +1 -1
  46. data/vendor/datasketches-cpp/hll/include/Hll6Array-internal.hpp +3 -3
  47. data/vendor/datasketches-cpp/hll/include/Hll6Array.hpp +1 -1
  48. data/vendor/datasketches-cpp/hll/include/Hll8Array-internal.hpp +6 -3
  49. data/vendor/datasketches-cpp/hll/include/Hll8Array.hpp +1 -1
  50. data/vendor/datasketches-cpp/hll/include/HllArray-internal.hpp +35 -29
  51. data/vendor/datasketches-cpp/hll/include/HllArray.hpp +1 -1
  52. data/vendor/datasketches-cpp/hll/include/HllSketch-internal.hpp +3 -5
  53. data/vendor/datasketches-cpp/hll/include/HllSketchImpl-internal.hpp +5 -11
  54. data/vendor/datasketches-cpp/hll/include/HllSketchImpl.hpp +2 -4
  55. data/vendor/datasketches-cpp/hll/include/HllSketchImplFactory.hpp +11 -10
  56. data/vendor/datasketches-cpp/hll/include/HllUnion-internal.hpp +17 -8
  57. data/vendor/datasketches-cpp/hll/include/HllUtil.hpp +12 -3
  58. data/vendor/datasketches-cpp/hll/include/coupon_iterator-internal.hpp +2 -2
  59. data/vendor/datasketches-cpp/hll/include/coupon_iterator.hpp +3 -0
  60. data/vendor/datasketches-cpp/hll/include/hll.hpp +11 -4
  61. data/vendor/datasketches-cpp/hll/include/hll.private.hpp +19 -0
  62. data/vendor/datasketches-cpp/hll/test/CMakeLists.txt +2 -0
  63. data/vendor/datasketches-cpp/hll/test/CouponListTest.cpp +64 -0
  64. data/vendor/datasketches-cpp/hll/test/HllFullSizeTest.cpp +137 -0
  65. data/vendor/datasketches-cpp/hll/test/HllKxqRebuildTest.cpp +150 -0
  66. data/vendor/datasketches-cpp/hll/test/HllSketchTest.cpp +3 -3
  67. data/vendor/datasketches-cpp/hll/test/HllUnionTest.cpp +71 -1
  68. data/vendor/datasketches-cpp/kll/include/kll_helper_impl.hpp +10 -10
  69. data/vendor/datasketches-cpp/kll/include/kll_sketch.hpp +10 -1
  70. data/vendor/datasketches-cpp/kll/include/kll_sketch_impl.hpp +33 -24
  71. data/vendor/datasketches-cpp/kll/test/kll_sketch_deserialize_from_java_test.cpp +24 -0
  72. data/vendor/datasketches-cpp/kll/test/kll_sketch_serialize_for_java.cpp +10 -0
  73. data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch.hpp +11 -2
  74. data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch_impl.hpp +24 -15
  75. data/vendor/datasketches-cpp/req/include/req_sketch.hpp +9 -0
  76. data/vendor/datasketches-cpp/req/include/req_sketch_impl.hpp +24 -15
  77. data/vendor/datasketches-cpp/req/test/req_sketch_deserialize_from_java_test.cpp +46 -0
  78. data/vendor/datasketches-cpp/req/test/req_sketch_serialize_for_java.cpp +20 -0
  79. data/vendor/datasketches-cpp/req/test/req_sketch_test.cpp +70 -0
  80. data/vendor/datasketches-cpp/sampling/include/ebpps_sample_impl.hpp +17 -8
  81. data/vendor/datasketches-cpp/sampling/include/ebpps_sketch.hpp +13 -0
  82. data/vendor/datasketches-cpp/sampling/include/var_opt_sketch.hpp +10 -1
  83. data/vendor/datasketches-cpp/sampling/include/var_opt_sketch_impl.hpp +6 -7
  84. data/vendor/datasketches-cpp/sampling/include/var_opt_union.hpp +5 -1
  85. data/vendor/datasketches-cpp/sampling/include/var_opt_union_impl.hpp +2 -1
  86. data/vendor/datasketches-cpp/sampling/test/ebpps_allocation_test.cpp +1 -1
  87. data/vendor/datasketches-cpp/sampling/test/ebpps_sketch_test.cpp +2 -2
  88. data/vendor/datasketches-cpp/sampling/test/var_opt_allocation_test.cpp +1 -1
  89. data/vendor/datasketches-cpp/sampling/test/var_opt_sketch_test.cpp +10 -4
  90. data/vendor/datasketches-cpp/sampling/test/var_opt_union_test.cpp +12 -0
  91. data/vendor/datasketches-cpp/tdigest/include/tdigest.hpp +38 -2
  92. data/vendor/datasketches-cpp/tdigest/include/tdigest_impl.hpp +168 -9
  93. data/vendor/datasketches-cpp/tdigest/test/CMakeLists.txt +1 -0
  94. data/vendor/datasketches-cpp/tdigest/test/tdigest_iterator_test.cpp +274 -0
  95. data/vendor/datasketches-cpp/tdigest/test/tdigest_test.cpp +275 -0
  96. data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser.hpp +2 -0
  97. data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser_impl.hpp +24 -3
  98. data/vendor/datasketches-cpp/theta/include/theta_constants.hpp +4 -2
  99. data/vendor/datasketches-cpp/theta/include/theta_helpers.hpp +32 -0
  100. data/vendor/datasketches-cpp/theta/include/theta_set_difference_base_impl.hpp +4 -2
  101. data/vendor/datasketches-cpp/theta/include/theta_sketch.hpp +22 -4
  102. data/vendor/datasketches-cpp/theta/include/theta_sketch_impl.hpp +60 -38
  103. data/vendor/datasketches-cpp/theta/include/theta_union_base_impl.hpp +2 -6
  104. data/vendor/datasketches-cpp/theta/include/theta_update_sketch_base_impl.hpp +2 -2
  105. data/vendor/datasketches-cpp/theta/test/bit_packing_test.cpp +50 -0
  106. data/vendor/datasketches-cpp/theta/test/theta_a_not_b_test.cpp +22 -0
  107. data/vendor/datasketches-cpp/theta/test/theta_sketch_test.cpp +315 -0
  108. data/vendor/datasketches-cpp/tools/rat-check.sh +68 -0
  109. data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch.hpp +35 -4
  110. data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch_impl.hpp +2 -2
  111. data/vendor/datasketches-cpp/tuple/include/tuple_sketch.hpp +41 -0
  112. data/vendor/datasketches-cpp/tuple/include/tuple_sketch_impl.hpp +5 -4
  113. data/vendor/datasketches-cpp/tuple/test/tuple_sketch_test.cpp +59 -0
  114. data/vendor/datasketches-cpp/version.cfg.in +1 -1
  115. metadata +12 -2
@@ -43,7 +43,16 @@ static const uint8_t FAMILY_ID = 7;
43
43
  static const uint8_t EMPTY_FLAG_MASK = 4;
44
44
  static const uint8_t COMPACT_FLAG_MASK = 8;
45
45
  static const uint8_t OUT_OF_ORDER_FLAG_MASK = 16;
46
- static const uint8_t FULL_SIZE_FLAG_MASK = 32;
46
+ // Bit 32 is RESERVED and must not be written or interpreted.
47
+ // It was formerly FULL_SIZE_FLAG_MASK here, while datasketches-java uses the same bit as
48
+ // REBUILD_CURMIN_NUM_KXQ_MASK for its union gadget. The two meanings collided across
49
+ // implementations, so this side no longer writes or reads it. Do not reuse: bits 64 and 128
50
+ // are free.
51
+ static const uint8_t RESERVED_FLAG_MASK_32 = 32;
52
+ // Deprecated alias for the bit above, retained for source compatibility. Do not use.
53
+ // Added 2026-09-07 (#521), first shipping in the release after 5.2.0. No [[deprecated]]:
54
+ // this library targets C++11, so the alias warns no one.
55
+ static const uint8_t FULL_SIZE_FLAG_MASK = RESERVED_FLAG_MASK_32;
47
56
 
48
57
  static const uint32_t PREAMBLE_INTS_BYTE = 0;
49
58
  static const uint32_t SER_VER_BYTE = 1;
@@ -82,8 +91,8 @@ static const uint32_t EMPTY = 0;
82
91
  static const uint8_t MIN_LOG_K = 4;
83
92
  static const uint8_t MAX_LOG_K = 21;
84
93
 
85
- static const double HLL_HIP_RSE_FACTOR = 0.8325546; // sqrt(ln(2))
86
- static const double HLL_NON_HIP_RSE_FACTOR = 1.03896; // sqrt((3 * ln(2)) - 1)
94
+ static const double HLL_HIP_RSE_FACTOR = 0.8325546111576977; // sqrt(ln(2))
95
+ static const double HLL_NON_HIP_RSE_FACTOR = 1.0389617614136892; // sqrt((3 * ln(2)) - 1)
87
96
  static const double COUPON_RSE_FACTOR = 0.409; // at transition point not the asymptote
88
97
  static const double COUPON_RSE = COUPON_RSE_FACTOR / (1 << 13);
89
98
 
@@ -28,7 +28,7 @@ template<typename A>
28
28
  coupon_iterator<A>::coupon_iterator(const uint32_t* array, size_t array_size, size_t index, bool all):
29
29
  array_(array), array_size_(array_size), index_(index), all_(all) {
30
30
  while (index_ < array_size_) {
31
- if (all_ || array_[index_] != hll_constants::EMPTY) break;
31
+ if (all_ || array_[index_] != hll_constants::EMPTY) { break; }
32
32
  ++index_;
33
33
  }
34
34
  }
@@ -36,7 +36,7 @@ array_(array), array_size_(array_size), index_(index), all_(all) {
36
36
  template<typename A>
37
37
  coupon_iterator<A>& coupon_iterator<A>::operator++() {
38
38
  while (++index_ < array_size_) {
39
- if (all_ || array_[index_] != hll_constants::EMPTY) break;
39
+ if (all_ || array_[index_] != hll_constants::EMPTY) { break; }
40
40
  }
41
41
  return *this;
42
42
  }
@@ -20,6 +20,9 @@
20
20
  #ifndef _INTARRAYPAIRITERATOR_HPP_
21
21
  #define _INTARRAYPAIRITERATOR_HPP_
22
22
 
23
+ #include <cstdint>
24
+ #include <iterator>
25
+
23
26
  namespace datasketches {
24
27
 
25
28
  template<typename A>
@@ -120,7 +120,10 @@ class hll_sketch_alloc final {
120
120
  * @param tgt_type The HLL mode to use, if/when the sketch reaches that state
121
121
  * @param start_full_size Indicates whether to start in HLL mode,
122
122
  * keeping memory use constant (if HLL_6 or HLL_8) at the cost of
123
- * starting out using much more memory
123
+ * starting out using much more memory. This is a property of this
124
+ * constructor call only: it is not retained by the sketch and is not
125
+ * serialized, so reset() returns to coupon collection mode unless
126
+ * reset(true) is used.
124
127
  * @param allocator instance of an Allocator
125
128
  */
126
129
  explicit hll_sketch_alloc(uint8_t lg_config_k, target_hll_type tgt_type = HLL_4, bool start_full_size = false, const A& allocator = A());
@@ -160,7 +163,7 @@ class hll_sketch_alloc final {
160
163
  static hll_sketch_alloc deserialize(const void* bytes, size_t len, const A& allocator = A());
161
164
 
162
165
  //! Class destructor
163
- virtual ~hll_sketch_alloc();
166
+ ~hll_sketch_alloc();
164
167
 
165
168
  /**
166
169
  * Copy assignment operator
@@ -177,10 +180,14 @@ class hll_sketch_alloc final {
177
180
  hll_sketch_alloc& operator=(hll_sketch_alloc<A>&& other);
178
181
 
179
182
  /**
180
- * Resets the sketch to an empty state in coupon collection mode.
183
+ * Resets the sketch to an empty state.
181
184
  * Does not re-use existing internal objects.
185
+ * @param full_size if true, reset to an empty full-size HLL array, as
186
+ * the start_full_size constructor argument does; otherwise reset to
187
+ * coupon collection mode. Full size is not remembered across a reset
188
+ * or a serialization round trip, so it must be requested each time.
182
189
  */
183
- void reset();
190
+ void reset(bool full_size = false);
184
191
 
185
192
  // This is a convenience alias for users
186
193
  // The type returned by the following serialize method
@@ -1,3 +1,22 @@
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
+
1
20
  #ifndef _HLL_PRIVATE_HPP_
2
21
  #define _HLL_PRIVATE_HPP_
3
22
 
@@ -42,6 +42,8 @@ target_sources(hll_test
42
42
  CouponListTest.cpp
43
43
  CrossCountingTest.cpp
44
44
  HllArrayTest.cpp
45
+ HllFullSizeTest.cpp
46
+ HllKxqRebuildTest.cpp
45
47
  HllSketchTest.cpp
46
48
  HllUnionTest.cpp
47
49
  TablesTest.cpp
@@ -24,6 +24,7 @@
24
24
  #include <string>
25
25
  #include <exception>
26
26
  #include <stdexcept>
27
+ #include <vector>
27
28
 
28
29
  #include "hll.hpp"
29
30
  #include "CouponList.hpp"
@@ -180,4 +181,67 @@ TEST_CASE("coupon list: check corrupt stream data", "[coupon_list]") {
180
181
  REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
181
182
  }
182
183
 
184
+ TEST_CASE("coupon list: rejects malformed coupon count in bytes", "[coupon_list]") {
185
+ // Reject declared LIST counts at or beyond the fixed LIST capacity.
186
+ uint8_t lgK = 8;
187
+ hll_sketch sk1(lgK);
188
+ sk1.update(1);
189
+ sk1.update(2);
190
+ const auto validBytes = sk1.serialize_compact();
191
+
192
+ const uint8_t listCapacity = static_cast<uint8_t>(1u << hll_constants::LG_INIT_LIST_SIZE);
193
+ const uint8_t malformedCount = listCapacity;
194
+ auto sketchBytes = validBytes;
195
+ // Keep the input long enough to validate the declared LIST count.
196
+ const size_t requiredLen = hll_constants::LIST_INT_ARR_START
197
+ + malformedCount * sizeof(uint32_t);
198
+ if (sketchBytes.size() < requiredLen) {
199
+ sketchBytes.resize(requiredLen, 0);
200
+ }
201
+ sketchBytes[hll_constants::LIST_COUNT_BYTE] = malformedCount;
202
+
203
+ REQUIRE_THROWS_AS(
204
+ hll_sketch::deserialize(sketchBytes.data(), sketchBytes.size()),
205
+ std::invalid_argument);
206
+ REQUIRE_THROWS_AS(
207
+ CouponList<std::allocator<uint8_t>>::newList(
208
+ sketchBytes.data(), sketchBytes.size(), std::allocator<uint8_t>()),
209
+ std::invalid_argument);
210
+ }
211
+
212
+ TEST_CASE("coupon list: rejects malformed coupon count in stream", "[coupon_list]") {
213
+ uint8_t lgK = 8;
214
+ hll_sketch sk1(lgK);
215
+ sk1.update(1);
216
+ sk1.update(2);
217
+
218
+ const uint8_t listCapacity = static_cast<uint8_t>(1u << hll_constants::LG_INIT_LIST_SIZE);
219
+ const uint8_t malformedCount = listCapacity;
220
+ std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
221
+ sk1.serialize_compact(ss);
222
+
223
+ const size_t requiredLen = hll_constants::LIST_INT_ARR_START
224
+ + malformedCount * sizeof(uint32_t);
225
+ // Keep the stream long enough to validate the declared LIST count.
226
+ ss.seekp(0, std::ios::end);
227
+ const auto endPos = static_cast<size_t>(ss.tellp());
228
+ if (endPos < requiredLen) {
229
+ std::vector<char> pad(requiredLen - endPos, 0);
230
+ ss.write(pad.data(), pad.size());
231
+ }
232
+
233
+ ss.seekp(hll_constants::LIST_COUNT_BYTE);
234
+ ss.put(static_cast<char>(malformedCount));
235
+
236
+ ss.clear();
237
+ ss.seekg(0);
238
+ REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
239
+
240
+ ss.clear();
241
+ ss.seekg(0);
242
+ REQUIRE_THROWS_AS(
243
+ CouponList<std::allocator<uint8_t>>::newList(ss, std::allocator<uint8_t>()),
244
+ std::invalid_argument);
245
+ }
246
+
183
247
  } /* namespace datasketches */
@@ -0,0 +1,137 @@
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
+
22
+ #include "hll.hpp"
23
+
24
+ namespace datasketches {
25
+
26
+ // preamble byte 0 is preInts, which identifies the current mode
27
+ static const uint8_t LIST_PREINTS = 2;
28
+ static const uint8_t HLL_PREINTS = 10;
29
+ // preamble byte 5 is the flags byte; bit 32 is reserved and must never be written
30
+ static const size_t FLAGS_BYTE = 5;
31
+ static const uint8_t RESERVED_BIT_32 = 32;
32
+
33
+ static uint8_t preints_of(const hll_sketch& sk) {
34
+ return sk.serialize_compact()[0];
35
+ }
36
+
37
+ static hll_sketch make(uint8_t lg_k, target_hll_type type, bool full_size, uint64_t n) {
38
+ hll_sketch sk(lg_k, type, full_size);
39
+ for (uint64_t i = 0; i < n; ++i) sk.update(i);
40
+ return sk;
41
+ }
42
+
43
+ TEST_CASE("hll full size: reserved bit 32 is never written", "[hll_full_size]") {
44
+ for (auto type: {HLL_4, HLL_6, HLL_8}) {
45
+ for (bool full_size: {false, true}) {
46
+ for (uint64_t n: {uint64_t(0), uint64_t(5), uint64_t(5000)}) {
47
+ const hll_sketch sk = make(8, type, full_size, n);
48
+ REQUIRE((sk.serialize_compact()[FLAGS_BYTE] & RESERVED_BIT_32) == 0);
49
+ REQUIRE((sk.serialize_updatable()[FLAGS_BYTE] & RESERVED_BIT_32) == 0);
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ TEST_CASE("hll full size: start_full_size starts in HLL mode", "[hll_full_size]") {
56
+ REQUIRE(preints_of(make(8, HLL_8, true, 0)) == HLL_PREINTS);
57
+ REQUIRE(preints_of(make(8, HLL_8, false, 0)) == LIST_PREINTS);
58
+ }
59
+
60
+ TEST_CASE("hll full size: reset takes the mode as an argument", "[hll_full_size]") {
61
+ for (auto type: {HLL_4, HLL_6, HLL_8}) {
62
+ // full size is not remembered: a plain reset() returns to coupon collection mode
63
+ hll_sketch sk = make(8, type, true, 5000);
64
+ sk.reset();
65
+ REQUIRE(sk.is_empty());
66
+ REQUIRE(sk.get_estimate() == 0.0);
67
+ REQUIRE(preints_of(sk) == LIST_PREINTS);
68
+
69
+ // ...and must be asked for explicitly
70
+ hll_sketch sk2 = make(8, type, false, 5000);
71
+ sk2.reset(true);
72
+ REQUIRE(sk2.is_empty());
73
+ REQUIRE(sk2.get_estimate() == 0.0);
74
+ REQUIRE(preints_of(sk2) == HLL_PREINTS);
75
+ REQUIRE(sk2.get_lg_config_k() == 8);
76
+ REQUIRE(sk2.get_target_type() == type);
77
+
78
+ // reset(false) is the same as reset()
79
+ hll_sketch sk3 = make(8, type, true, 5000);
80
+ sk3.reset(false);
81
+ REQUIRE(preints_of(sk3) == LIST_PREINTS);
82
+ }
83
+ }
84
+
85
+ TEST_CASE("hll full size: a reset sketch is usable again", "[hll_full_size]") {
86
+ hll_sketch sk = make(8, HLL_8, false, 100);
87
+ sk.reset(true);
88
+ for (uint64_t i = 0; i < 1000; ++i) sk.update(i);
89
+ REQUIRE_FALSE(sk.is_empty());
90
+ REQUIRE(sk.get_estimate() == Approx(1000).epsilon(0.2));
91
+ }
92
+
93
+ TEST_CASE("hll full size: an image with the reserved bit set is read as if it were clear",
94
+ "[hll_full_size]") {
95
+ // an image produced by another implementation may have bit 32 set: datasketches-java uses it
96
+ // as its union rebuild flag. It must not change how this implementation reads the sketch.
97
+ for (auto type: {HLL_4, HLL_6, HLL_8}) {
98
+ const hll_sketch sk = make(8, type, false, 5000);
99
+ auto bytes = sk.serialize_updatable();
100
+ auto tampered = bytes;
101
+ tampered[FLAGS_BYTE] |= RESERVED_BIT_32;
102
+
103
+ const hll_sketch clean = hll_sketch::deserialize(bytes.data(), bytes.size());
104
+ hll_sketch tainted = hll_sketch::deserialize(tampered.data(), tampered.size());
105
+
106
+ REQUIRE(tainted.get_estimate() == clean.get_estimate());
107
+ REQUIRE(tainted.serialize_updatable() == bytes); // the bit is not propagated back out
108
+ tainted.reset();
109
+ REQUIRE(preints_of(tainted) == LIST_PREINTS); // and does not alter reset() behaviour
110
+ }
111
+ }
112
+
113
+ TEST_CASE("hll full size: a union never inherits full size from an input sketch",
114
+ "[hll_full_size]") {
115
+ // the gadget is an internal detail; unioning a full-size sketch must not change how the
116
+ // union resets, nor put the reserved bit into the union's result
117
+ const hll_sketch full = make(8, HLL_8, true, 5000);
118
+
119
+ hll_union u(8);
120
+ u.update(full);
121
+ const hll_sketch result = u.get_result(HLL_8);
122
+ REQUIRE((result.serialize_compact()[FLAGS_BYTE] & RESERVED_BIT_32) == 0);
123
+ REQUIRE((result.serialize_updatable()[FLAGS_BYTE] & RESERVED_BIT_32) == 0);
124
+
125
+ u.reset();
126
+ REQUIRE(u.is_empty());
127
+ REQUIRE(preints_of(u.get_result(HLL_8)) == LIST_PREINTS);
128
+
129
+ // same through the rvalue overload, which moves the sketch into the gadget
130
+ hll_union u2(8);
131
+ u2.update(make(8, HLL_8, true, 5000));
132
+ u2.reset();
133
+ REQUIRE(u2.is_empty());
134
+ REQUIRE(preints_of(u2.get_result(HLL_8)) == LIST_PREINTS);
135
+ }
136
+
137
+ } /* namespace datasketches */
@@ -0,0 +1,150 @@
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 <cmath>
22
+ #include <cstring>
23
+ #include <limits>
24
+
25
+ #include "hll.hpp"
26
+ #include "fdlibm_log.hpp"
27
+
28
+ namespace datasketches {
29
+
30
+ // offsets into the HLL updatable image
31
+ static const size_t CUR_MIN_BYTE = 6;
32
+ static const size_t NUM_AT_CUR_MIN_INT = 32;
33
+ static const size_t HLL_BYTE_ARR_START = 40;
34
+
35
+ static hll_sketch make(uint8_t lg_k, target_hll_type type, uint64_t lo, uint64_t hi) {
36
+ hll_sketch sk(lg_k, type);
37
+ for (uint64_t i = lo; i < hi; ++i) sk.update(i);
38
+ return sk;
39
+ }
40
+ static uint32_t num_at_cur_min_of(const hll_sketch::vector_bytes& img) {
41
+ uint32_t v; std::memcpy(&v, img.data() + NUM_AT_CUR_MIN_INT, sizeof(v)); return v;
42
+ }
43
+
44
+ TEST_CASE("hll kxq rebuild: union result is merge-order independent", "[hll_kxq]") {
45
+ // C stays in SET mode, so this exercises the coupon-update path into a gadget with a
46
+ // deferred rebuild pending, which is where the stored curMin/numAtCurMin used to drift
47
+ const hll_sketch a = make(12, HLL_4, 20000, 30364);
48
+ const hll_sketch b = make(10, HLL_8, 5000, 14699);
49
+ const hll_sketch c = make(17, HLL_4, 70000, 72598);
50
+ const hll_sketch* in[3] = {&a, &b, &c};
51
+
52
+ auto run = [&](int i, int j, int k) {
53
+ hll_union u(7);
54
+ u.update(*in[i]); u.update(*in[j]); u.update(*in[k]);
55
+ return u.get_result(HLL_8).serialize_updatable();
56
+ };
57
+ const auto ref = run(0, 1, 2);
58
+ REQUIRE(run(0, 2, 1) == ref);
59
+ REQUIRE(run(1, 0, 2) == ref);
60
+ REQUIRE(run(1, 2, 0) == ref);
61
+ REQUIRE(run(2, 0, 1) == ref);
62
+ REQUIRE(run(2, 1, 0) == ref);
63
+ }
64
+
65
+ TEST_CASE("hll kxq rebuild: reading an estimate does not change a later result",
66
+ "[hll_kxq]") {
67
+ // the rebuild is lazy; when it fires must not be observable in the serialized image
68
+ const hll_sketch p = make(13, HLL_8, 0, 50000);
69
+ const hll_sketch q = make(13, HLL_8, 50000, 100000);
70
+
71
+ for (uint8_t lg_max_k: {uint8_t(7), uint8_t(8), uint8_t(9)}) {
72
+ hll_union peeked(lg_max_k);
73
+ peeked.update(p); peeked.update(q);
74
+ (void) peeked.get_estimate(); // forces the rebuild here
75
+ for (uint64_t v = 9000000; v < 9400000; ++v) peeked.update(v);
76
+
77
+ hll_union plain(lg_max_k);
78
+ plain.update(p); plain.update(q);
79
+ for (uint64_t v = 9000000; v < 9400000; ++v) plain.update(v);
80
+
81
+ REQUIRE(peeked.get_result(HLL_8).serialize_updatable()
82
+ == plain.get_result(HLL_8).serialize_updatable());
83
+ }
84
+ }
85
+
86
+ TEST_CASE("hll kxq rebuild: stored curMin and numAtCurMin agree with the registers",
87
+ "[hll_kxq]") {
88
+ // HLL_8 convention: curMin is always 0 and numAtCurMin counts the zero registers
89
+ const hll_sketch a = make(15, HLL_8, 0, 100000);
90
+ const hll_sketch b = make(8, HLL_8, 100000, 200000);
91
+ hll_union u(8);
92
+ u.update(a); u.update(b);
93
+ const auto img = u.get_result(HLL_8).serialize_updatable();
94
+
95
+ uint32_t zeros = 0;
96
+ for (size_t i = HLL_BYTE_ARR_START; i < img.size(); ++i) if (img[i] == 0) ++zeros;
97
+
98
+ REQUIRE(img[CUR_MIN_BYTE] == 0);
99
+ REQUIRE(num_at_cur_min_of(img) == zeros);
100
+ }
101
+
102
+ TEST_CASE("hll kxq rebuild: relative error constants are full precision", "[hll_kxq]") {
103
+ // lg_k > 12 uses the closed form rather than the interpolation table, so a constant
104
+ // truncated to seven digits is directly observable in the bounds
105
+ const double hip = std::sqrt(std::log(2.0)); // sqrt(ln 2)
106
+ const double non_hip = std::sqrt((3.0 * std::log(2.0)) - 1.0); // sqrt(3 ln 2 - 1)
107
+
108
+ for (uint8_t lg_k: {uint8_t(13), uint8_t(16), uint8_t(21)}) {
109
+ const double k = static_cast<double>(1 << lg_k);
110
+ for (uint8_t sd = 1; sd <= 3; ++sd) {
111
+ const double got_hip = hll_union::get_rel_err(false, false, lg_k, sd);
112
+ const double got_non = hll_union::get_rel_err(false, true, lg_k, sd);
113
+ REQUIRE(got_hip == Approx(sd * hip / std::sqrt(k)).epsilon(1e-15));
114
+ REQUIRE(got_non == Approx(sd * non_hip / std::sqrt(k)).epsilon(1e-15));
115
+ }
116
+ }
117
+ }
118
+
119
+ TEST_CASE("hll kxq rebuild: log matches the fdlibm reference", "[hll_kxq]") {
120
+ // the linear counting estimator subtracts two nearby harmonic numbers, which amplifies a
121
+ // 1 ULP difference in log() by more than an order of magnitude. Pin log() to fdlibm, the
122
+ // function datasketches-java's StrictMath.log is specified to be. These expected values
123
+ // were taken from Java; the platform libm differs from every one of them.
124
+ struct { int x; uint64_t bits; } expected[] = {
125
+ { 48, 0x400ef8383c50bb74ULL},
126
+ { 74, 0x4011375cd6fcab1cULL},
127
+ { 185, 0x4014e1a4f518c72cULL},
128
+ { 196, 0x40151cca16d7bba8ULL},
129
+ { 299, 0x4016cd411481a020ULL},
130
+ { 308, 0x4016eb9f470ac0b8ULL},
131
+ { 334, 0x40173e9bbe951e9cULL},
132
+ { 343, 0x401759d602a5c3c2ULL},
133
+ { 1261, 0x401c8f031e7e1220ULL},
134
+ };
135
+ for (const auto& e: expected) {
136
+ const double got = fdlibm::log(static_cast<double>(e.x));
137
+ uint64_t bits; std::memcpy(&bits, &got, sizeof(bits));
138
+ REQUIRE(bits == e.bits);
139
+ }
140
+
141
+ // edge cases: fdlibm produces these by dividing by a zero constant, which MSVC rejects at
142
+ // compile time, so they are returned directly. Pin the values.
143
+ REQUIRE(fdlibm::log(0.0) == -std::numeric_limits<double>::infinity());
144
+ REQUIRE(fdlibm::log(-0.0) == -std::numeric_limits<double>::infinity());
145
+ REQUIRE(std::isnan(fdlibm::log(-1.0)));
146
+ REQUIRE(fdlibm::log(1.0) == 0.0);
147
+ REQUIRE(fdlibm::log(5e-320) == Approx(-735.2178).epsilon(1e-6)); // subnormal scaling path
148
+ }
149
+
150
+ } /* namespace datasketches */
@@ -298,7 +298,7 @@ TEST_CASE("hll sketch: deserialize list mode buffer overrun", "[hll_sketch]") {
298
298
  REQUIRE_THROWS_AS(hll_sketch_test_alloc::deserialize(bytes.data(), 7, 0), std::out_of_range);
299
299
  REQUIRE_THROWS_AS(hll_sketch_test_alloc::deserialize(bytes.data(), bytes.size() - 1, 0), std::out_of_range);
300
300
 
301
- // ckeck for leaks on stream exceptions
301
+ // check for leaks on stream exceptions
302
302
  {
303
303
  std::stringstream ss;
304
304
  ss.exceptions(std::ios::failbit | std::ios::badbit);
@@ -325,7 +325,7 @@ TEST_CASE("hll sketch: deserialize set mode buffer overrun", "[hll_sketch]") {
325
325
  REQUIRE_THROWS_AS(hll_sketch_test_alloc::deserialize(bytes.data(), 7, 0), std::out_of_range);
326
326
  REQUIRE_THROWS_AS(hll_sketch_test_alloc::deserialize(bytes.data(), bytes.size() - 1, 0), std::out_of_range);
327
327
 
328
- // ckeck for leaks on stream exceptions
328
+ // check for leaks on stream exceptions
329
329
  {
330
330
  std::stringstream ss;
331
331
  ss.exceptions(std::ios::failbit | std::ios::badbit);
@@ -355,7 +355,7 @@ TEST_CASE("hll sketch: deserialize HLL mode buffer overrun", "[hll_sketch]") {
355
355
  REQUIRE_THROWS_AS(hll_sketch_test_alloc::deserialize(bytes.data(), 16420, 0), std::out_of_range); // before aux table
356
356
  REQUIRE_THROWS_AS(hll_sketch_test_alloc::deserialize(bytes.data(), bytes.size() - 1, 0), std::out_of_range);
357
357
 
358
- // ckeck for leaks on stream exceptions
358
+ // check for leaks on stream exceptions
359
359
  {
360
360
  std::stringstream ss;
361
361
  ss.exceptions(std::ios::failbit | std::ios::badbit);
@@ -58,7 +58,7 @@ static void basicUnion(uint64_t n1, uint64_t n2,
58
58
 
59
59
  hll_sketch result = u.get_result(resultType);
60
60
 
61
- // ensure we check a direct union estimate, without first caling get_result()
61
+ // ensure we check a direct union estimate, without first calling get_result()
62
62
  u.reset();
63
63
  u.update(std::move(h1));
64
64
  u.update(h2);
@@ -314,4 +314,74 @@ TEST_CASE("hll union: check hll to hll", "[hll_union]") {
314
314
  union_two_sketches_with_overlap(1000000, 11, HLL_4);
315
315
  }
316
316
 
317
+ static hll_sketch make_hll_sketch(uint8_t lg_config_k, uint64_t start, uint64_t end) {
318
+ hll_sketch sketch(lg_config_k);
319
+ for (uint64_t i = start; i < end; ++i) sketch.update(i);
320
+ return sketch;
321
+ }
322
+
323
+ static hll_sketch::vector_bytes serialize_flat_union(
324
+ const hll_sketch& first, const hll_sketch& second, const hll_sketch& third) {
325
+ hll_union u(8);
326
+ u.update(first);
327
+ u.update(second);
328
+ u.update(third);
329
+ return u.get_result(HLL_8).serialize_updatable();
330
+ }
331
+
332
+ static hll_sketch::vector_bytes serialize_nested_union(
333
+ const hll_sketch& first, const hll_sketch& second, const hll_sketch& third) {
334
+ hll_union prefix(8);
335
+ prefix.update(first);
336
+ prefix.update(second);
337
+
338
+ hll_union u(8);
339
+ u.update(prefix.get_result(HLL_8));
340
+ u.update(third);
341
+ return u.get_result(HLL_8).serialize_updatable();
342
+ }
343
+
344
+ TEST_CASE("hll union: downsampling merge is not empty", "[hll_union]") {
345
+ const hll_sketch sketch = make_hll_sketch(15, 0, 100000);
346
+ hll_union u(8);
347
+ u.update(sketch);
348
+ REQUIRE_FALSE(u.is_empty());
349
+ }
350
+
351
+ TEST_CASE("hll union: mixed lgConfigK estimate is merge-order independent", "[hll_union]") {
352
+ const uint64_t n = 100000;
353
+ const hll_sketch a = make_hll_sketch(15, 0, n);
354
+ const hll_sketch b = make_hll_sketch(8, n, 2 * n);
355
+ const double truth = 2.0 * n;
356
+
357
+ hll_union larger_first(8);
358
+ larger_first.update(a);
359
+ larger_first.update(b);
360
+ REQUIRE(larger_first.get_estimate() == Approx(truth).epsilon(0.1));
361
+
362
+ hll_union smaller_first(8);
363
+ smaller_first.update(b);
364
+ smaller_first.update(a);
365
+ REQUIRE(smaller_first.get_estimate() == Approx(truth).epsilon(0.1));
366
+ }
367
+
368
+ TEST_CASE("hll union: scalar update after downsampling merge", "[hll_union]") {
369
+ const uint64_t n = 100000;
370
+ const hll_sketch sketch = make_hll_sketch(15, 0, n);
371
+ hll_union u(8);
372
+ u.update(sketch);
373
+ for (uint64_t i = n; i < 2 * n; ++i) u.update(i);
374
+ REQUIRE(u.get_estimate() == Approx(2.0 * n).epsilon(0.1));
375
+ }
376
+
377
+ TEST_CASE("hll union: serialization is grouping independent", "[hll_union]") {
378
+ const uint64_t n = 100000;
379
+ const hll_sketch a = make_hll_sketch(15, 0, n);
380
+ const hll_sketch b = make_hll_sketch(8, n, 2 * n);
381
+ const hll_sketch c = make_hll_sketch(11, n / 2, n + n / 2);
382
+ REQUIRE(serialize_nested_union(b, c, a) == serialize_flat_union(b, c, a));
383
+ REQUIRE(serialize_nested_union(a, c, b) == serialize_flat_union(a, c, b));
384
+ REQUIRE(serialize_nested_union(a, b, c) == serialize_flat_union(a, b, c));
385
+ }
386
+
317
387
  } /* namespace datasketches */
@@ -36,17 +36,17 @@ bool kll_helper::is_odd(uint32_t value) {
36
36
  }
37
37
 
38
38
  uint8_t kll_helper::floor_of_log2_of_fraction(uint64_t numer, uint64_t denom) {
39
- if (denom > numer) return 0;
39
+ if (denom > numer) { return 0; }
40
40
  uint8_t count = 0;
41
41
  while (true) {
42
42
  denom <<= 1;
43
- if (denom > numer) return count;
43
+ if (denom > numer) { return count; }
44
44
  count++;
45
45
  }
46
46
  }
47
47
 
48
48
  uint8_t kll_helper::ub_on_num_levels(uint64_t n) {
49
- if (n == 0) return 1;
49
+ if (n == 0) { return 1; }
50
50
  return 1 + floor_of_log2_of_fraction(n, 1);
51
51
  }
52
52
 
@@ -65,8 +65,8 @@ uint16_t kll_helper::level_capacity(uint16_t k, uint8_t numLevels, uint8_t heigh
65
65
  }
66
66
 
67
67
  uint16_t kll_helper::int_cap_aux(uint16_t k, uint8_t depth) {
68
- if (depth > 60) throw std::invalid_argument("depth > 60");
69
- if (depth <= 30) return int_cap_aux_aux(k, depth);
68
+ if (depth > 60) { throw std::invalid_argument("depth > 60"); }
69
+ if (depth <= 30) { return int_cap_aux_aux(k, depth); }
70
70
  const uint8_t half = depth / 2;
71
71
  const uint8_t rest = depth - half;
72
72
  const uint16_t tmp = int_cap_aux_aux(k, half);
@@ -74,11 +74,11 @@ uint16_t kll_helper::int_cap_aux(uint16_t k, uint8_t depth) {
74
74
  }
75
75
 
76
76
  uint16_t kll_helper::int_cap_aux_aux(uint16_t k, uint8_t depth) {
77
- if (depth > 30) throw std::invalid_argument("depth > 30");
77
+ if (depth > 30) { throw std::invalid_argument("depth > 30"); }
78
78
  const uint64_t twok = k << 1; // for rounding, we pre-multiply by 2
79
79
  const uint64_t tmp = (uint64_t) (((uint64_t) twok << depth) / powers_of_three[depth]);
80
80
  const uint64_t result = (tmp + 1) >> 1; // then here we add 1 and divide by 2
81
- if (result > k) throw std::logic_error("result > k");
81
+ if (result > k) { throw std::logic_error("result > k"); }
82
82
  return static_cast<uint16_t>(result);
83
83
  }
84
84
 
@@ -94,7 +94,7 @@ uint64_t kll_helper::sum_the_sample_weights(uint8_t num_levels, const uint32_t*
94
94
 
95
95
  template <typename T>
96
96
  void kll_helper::randomly_halve_down(T* buf, uint32_t start, uint32_t length) {
97
- if (!is_even(length)) throw std::invalid_argument("length must be even");
97
+ if (!is_even(length)) { throw std::invalid_argument("length must be even"); }
98
98
  const uint32_t half_length = length / 2;
99
99
  #ifdef KLL_VALIDATION
100
100
  const uint32_t offset = deterministic_offset();
@@ -110,7 +110,7 @@ void kll_helper::randomly_halve_down(T* buf, uint32_t start, uint32_t length) {
110
110
 
111
111
  template <typename T>
112
112
  void kll_helper::randomly_halve_up(T* buf, uint32_t start, uint32_t length) {
113
- if (!is_even(length)) throw std::invalid_argument("length must be even");
113
+ if (!is_even(length)) { throw std::invalid_argument("length must be even"); }
114
114
  const uint32_t half_length = length / 2;
115
115
  #ifdef KLL_VALIDATION
116
116
  const uint32_t offset = deterministic_offset();
@@ -206,7 +206,7 @@ template <typename T, typename C>
206
206
  kll_helper::compress_result kll_helper::general_compress(uint16_t k, uint8_t m, uint8_t num_levels_in, T* items,
207
207
  uint32_t* in_levels, uint32_t* out_levels, bool is_level_zero_sorted)
208
208
  {
209
- if (num_levels_in == 0) throw std::invalid_argument("num_levels_in == 0"); // things are too weird if zero levels are allowed
209
+ if (num_levels_in == 0) { throw std::invalid_argument("num_levels_in == 0"); } // things are too weird if zero levels are allowed
210
210
  const uint32_t starting_item_count = in_levels[num_levels_in] - in_levels[0];
211
211
  uint8_t current_num_levels = num_levels_in;
212
212
  uint32_t current_item_count = starting_item_count; // decreases with each compaction