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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/ext/datasketches/theta_wrapper.cpp +1 -1
- 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
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
#define COUNT_MIN_HPP_
|
|
22
22
|
|
|
23
23
|
#include <iterator>
|
|
24
|
+
#include <type_traits>
|
|
25
|
+
#include <vector>
|
|
24
26
|
#include "common_defs.hpp"
|
|
25
27
|
|
|
26
28
|
namespace datasketches {
|
|
@@ -36,6 +38,7 @@ template <typename W,
|
|
|
36
38
|
typename Allocator = std::allocator<W>>
|
|
37
39
|
class count_min_sketch{
|
|
38
40
|
static_assert(std::is_arithmetic<W>::value, "Arithmetic type expected");
|
|
41
|
+
static_assert(!std::is_same<W, bool>::value, "Boolean weight type is not supported");
|
|
39
42
|
public:
|
|
40
43
|
using allocator_type = Allocator;
|
|
41
44
|
using const_iterator = typename std::vector<W, Allocator>::const_iterator;
|
|
@@ -326,6 +329,15 @@ public:
|
|
|
326
329
|
*/
|
|
327
330
|
void serialize(std::ostream& os) const;
|
|
328
331
|
|
|
332
|
+
/**
|
|
333
|
+
* This method serializes the sketch by passing binary fragments to a callback.
|
|
334
|
+
* The callback must be callable as write_bytes(const void* data, size_t size).
|
|
335
|
+
* The data pointer is valid only until the callback returns.
|
|
336
|
+
* @return size in bytes written to the callback
|
|
337
|
+
*/
|
|
338
|
+
template<typename Sink>
|
|
339
|
+
size_t serialize_to(Sink&& sink) const;
|
|
340
|
+
|
|
329
341
|
// This is a convenience alias for users
|
|
330
342
|
// The type returned by the following serialize method
|
|
331
343
|
using vector_bytes = std::vector<uint8_t, typename std::allocator_traits<Allocator>::template rebind_alloc<uint8_t>>;
|
|
@@ -389,12 +401,13 @@ private:
|
|
|
389
401
|
static void check_header_validity(uint8_t preamble_longs, uint8_t serial_version, uint8_t family_id, uint8_t flags_byte);
|
|
390
402
|
|
|
391
403
|
/*
|
|
392
|
-
*
|
|
393
|
-
* @param item pointer to the data item to be inserted into the sketch.
|
|
404
|
+
* Compute the hash locations for an input item
|
|
405
|
+
* @param item pointer to the data item to be inserted into or queried from the sketch.
|
|
394
406
|
* @param size of the data in bytes
|
|
395
|
-
* @
|
|
407
|
+
* @param callback function to invoke for each sketch array location
|
|
396
408
|
*/
|
|
397
|
-
|
|
409
|
+
template<typename F>
|
|
410
|
+
void foreach_hash_location(const void* item, size_t size, F callback) const;
|
|
398
411
|
|
|
399
412
|
};
|
|
400
413
|
|
|
@@ -36,14 +36,16 @@ count_min_sketch<W,A>::count_min_sketch(uint8_t num_hashes, uint32_t num_buckets
|
|
|
36
36
|
_allocator(allocator),
|
|
37
37
|
_num_hashes(num_hashes),
|
|
38
38
|
_num_buckets(num_buckets),
|
|
39
|
-
_sketch_array((num_hashes*num_buckets < 1<<30) ? num_hashes*num_buckets : 0, 0, _allocator),
|
|
39
|
+
_sketch_array((static_cast<uint64_t>(num_hashes) * num_buckets < 1<<30) ? static_cast<size_t>(num_hashes) * num_buckets : 0, 0, _allocator),
|
|
40
40
|
_seed(seed),
|
|
41
41
|
_total_weight(0) {
|
|
42
|
-
if (num_buckets < 3)
|
|
42
|
+
if (num_buckets < 3) {
|
|
43
|
+
throw std::invalid_argument("Using fewer than 3 buckets incurs relative error greater than 1.");
|
|
44
|
+
}
|
|
43
45
|
|
|
44
46
|
// This check is to ensure later compatibility with a Java implementation whose maximum size can only
|
|
45
47
|
// be 2^31-1. We check only against 2^30 for simplicity.
|
|
46
|
-
if (num_buckets * num_hashes >= 1 << 30) {
|
|
48
|
+
if (static_cast<uint64_t>(num_buckets) * num_hashes >= 1 << 30) {
|
|
47
49
|
throw std::invalid_argument("These parameters generate a sketch that exceeds 2^30 elements."
|
|
48
50
|
"Try reducing either the number of buckets or the number of hash functions.");
|
|
49
51
|
}
|
|
@@ -74,7 +76,7 @@ uint64_t count_min_sketch<W,A>::get_seed() const {
|
|
|
74
76
|
|
|
75
77
|
template<typename W, typename A>
|
|
76
78
|
double count_min_sketch<W,A>::get_relative_error() const {
|
|
77
|
-
return exp(1.0) / double(_num_buckets);
|
|
79
|
+
return exp(1.0) / static_cast<double>(_num_buckets);
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
template<typename W, typename A>
|
|
@@ -108,9 +110,10 @@ uint8_t count_min_sketch<W,A>::suggest_num_hashes(double confidence) {
|
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
template<typename W, typename A>
|
|
111
|
-
|
|
113
|
+
template<typename F>
|
|
114
|
+
void count_min_sketch<W,A>::foreach_hash_location(const void* item, size_t size, F callback) const {
|
|
112
115
|
/*
|
|
113
|
-
*
|
|
116
|
+
* Computes the hash locations for the input item using the original hashing
|
|
114
117
|
* scheme from [1].
|
|
115
118
|
* Generate _num_hashes separate hashes from calls to murmurmhash.
|
|
116
119
|
* This could be optimized by keeping both of the 64bit parts of the hash
|
|
@@ -124,8 +127,6 @@ std::vector<uint64_t> count_min_sketch<W,A>::get_hashes(const void* item, size_t
|
|
|
124
127
|
* https://www.eecs.harvard.edu/~michaelm/postscripts/tr-02-05.pdf
|
|
125
128
|
*/
|
|
126
129
|
uint64_t bucket_index;
|
|
127
|
-
std::vector<uint64_t> sketch_update_locations;
|
|
128
|
-
sketch_update_locations.reserve(_num_hashes);
|
|
129
130
|
|
|
130
131
|
uint64_t hash_seed_index = 0;
|
|
131
132
|
for (const auto &it: hash_seeds) {
|
|
@@ -133,10 +134,9 @@ std::vector<uint64_t> count_min_sketch<W,A>::get_hashes(const void* item, size_t
|
|
|
133
134
|
MurmurHash3_x64_128(item, size, it, hashes); // ? BEWARE OVERFLOW.
|
|
134
135
|
uint64_t hash = hashes.h1;
|
|
135
136
|
bucket_index = hash % _num_buckets;
|
|
136
|
-
|
|
137
|
+
callback((hash_seed_index * _num_buckets) + bucket_index);
|
|
137
138
|
hash_seed_index += 1;
|
|
138
139
|
}
|
|
139
|
-
return sketch_update_locations;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
template<typename W, typename A>
|
|
@@ -147,7 +147,7 @@ W count_min_sketch<W,A>::get_estimate(int64_t item) const {return get_estimate(&
|
|
|
147
147
|
|
|
148
148
|
template<typename W, typename A>
|
|
149
149
|
W count_min_sketch<W,A>::get_estimate(const std::string& item) const {
|
|
150
|
-
if (item.empty()) return 0; // Empty strings are not inserted into the sketch.
|
|
150
|
+
if (item.empty()) { return 0; } // Empty strings are not inserted into the sketch.
|
|
151
151
|
return get_estimate(item.c_str(), item.length());
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -156,12 +156,11 @@ W count_min_sketch<W,A>::get_estimate(const void* item, size_t size) const {
|
|
|
156
156
|
/*
|
|
157
157
|
* Returns the estimated frequency of the item
|
|
158
158
|
*/
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return *std::min_element(estimates.begin(), estimates.end());
|
|
159
|
+
W estimate = std::numeric_limits<W>::max();
|
|
160
|
+
foreach_hash_location(item, size, [this, &estimate](uint64_t h) {
|
|
161
|
+
estimate = std::min(estimate, _sketch_array[h]);
|
|
162
|
+
});
|
|
163
|
+
return estimate;
|
|
165
164
|
}
|
|
166
165
|
|
|
167
166
|
template<typename W, typename A>
|
|
@@ -176,7 +175,7 @@ void count_min_sketch<W,A>::update(int64_t item, W weight) {
|
|
|
176
175
|
|
|
177
176
|
template<typename W, typename A>
|
|
178
177
|
void count_min_sketch<W,A>::update(const std::string& item, W weight) {
|
|
179
|
-
if (item.empty()) return;
|
|
178
|
+
if (item.empty()) { return; }
|
|
180
179
|
update(item.c_str(), item.length(), weight);
|
|
181
180
|
}
|
|
182
181
|
|
|
@@ -187,10 +186,9 @@ void count_min_sketch<W,A>::update(const void* item, size_t size, W weight) {
|
|
|
187
186
|
* locations by the weight.
|
|
188
187
|
*/
|
|
189
188
|
_total_weight += weight >= 0 ? weight : -weight;
|
|
190
|
-
|
|
191
|
-
for (const auto h: hash_locations) {
|
|
189
|
+
foreach_hash_location(item, size, [this, weight](uint64_t h) {
|
|
192
190
|
_sketch_array[h] += weight;
|
|
193
|
-
}
|
|
191
|
+
});
|
|
194
192
|
}
|
|
195
193
|
|
|
196
194
|
template<typename W, typename A>
|
|
@@ -201,7 +199,7 @@ W count_min_sketch<W,A>::get_upper_bound(int64_t item) const {return get_upper_b
|
|
|
201
199
|
|
|
202
200
|
template<typename W, typename A>
|
|
203
201
|
W count_min_sketch<W,A>::get_upper_bound(const std::string& item) const {
|
|
204
|
-
if (item.empty()) return 0; // Empty strings are not inserted into the sketch.
|
|
202
|
+
if (item.empty()) { return 0; } // Empty strings are not inserted into the sketch.
|
|
205
203
|
return get_upper_bound(item.c_str(), item.length());
|
|
206
204
|
}
|
|
207
205
|
|
|
@@ -218,7 +216,7 @@ W count_min_sketch<W,A>::get_lower_bound(int64_t item) const {return get_lower_b
|
|
|
218
216
|
|
|
219
217
|
template<typename W, typename A>
|
|
220
218
|
W count_min_sketch<W,A>::get_lower_bound(const std::string& item) const {
|
|
221
|
-
if (item.empty()) return 0; // Empty strings are not inserted into the sketch.
|
|
219
|
+
if (item.empty()) { return 0; } // Empty strings are not inserted into the sketch.
|
|
222
220
|
return get_lower_bound(item.c_str(), item.length());
|
|
223
221
|
}
|
|
224
222
|
|
|
@@ -232,17 +230,13 @@ void count_min_sketch<W,A>::merge(const count_min_sketch &other_sketch) {
|
|
|
232
230
|
/*
|
|
233
231
|
* Merges this sketch into other_sketch sketch by elementwise summing of buckets
|
|
234
232
|
*/
|
|
235
|
-
if (this == &other_sketch) {
|
|
236
|
-
throw std::invalid_argument( "Cannot merge a sketch with itself." );
|
|
237
|
-
}
|
|
233
|
+
if (this == &other_sketch) { throw std::invalid_argument( "Cannot merge a sketch with itself." ); }
|
|
238
234
|
|
|
239
235
|
bool acceptable_config =
|
|
240
236
|
(get_num_hashes() == other_sketch.get_num_hashes()) &&
|
|
241
237
|
(get_num_buckets() == other_sketch.get_num_buckets()) &&
|
|
242
238
|
(get_seed() == other_sketch.get_seed());
|
|
243
|
-
if (!acceptable_config) {
|
|
244
|
-
throw std::invalid_argument( "Incompatible sketch configuration." );
|
|
245
|
-
}
|
|
239
|
+
if (!acceptable_config) { throw std::invalid_argument( "Incompatible sketch configuration." ); }
|
|
246
240
|
|
|
247
241
|
// Merge step - iterate over the other vector and add the weights to this sketch
|
|
248
242
|
auto it = _sketch_array.begin(); // This is a std::vector iterator.
|
|
@@ -268,6 +262,22 @@ return _sketch_array.end();
|
|
|
268
262
|
|
|
269
263
|
template<typename W, typename A>
|
|
270
264
|
void count_min_sketch<W,A>::serialize(std::ostream& os) const {
|
|
265
|
+
serialize_to([&os](const void* data, size_t size) {
|
|
266
|
+
os.write(static_cast<const char*>(data), size);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
template<typename Sink, typename T>
|
|
271
|
+
static inline void write_count_min_value(Sink& sink, size_t& bytes_written, const T& value) {
|
|
272
|
+
sink(&value, sizeof(value));
|
|
273
|
+
bytes_written += sizeof(value);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
template<typename W, typename A>
|
|
277
|
+
template<typename Sink>
|
|
278
|
+
size_t count_min_sketch<W,A>::serialize_to(Sink&& sink) const {
|
|
279
|
+
size_t bytes_written = 0;
|
|
280
|
+
|
|
271
281
|
// Long 0
|
|
272
282
|
//const uint8_t preamble_longs = is_empty() ? PREAMBLE_LONGS_SHORT : PREAMBLE_LONGS_FULL;
|
|
273
283
|
const uint8_t preamble_longs = PREAMBLE_LONGS_SHORT;
|
|
@@ -275,32 +285,35 @@ void count_min_sketch<W,A>::serialize(std::ostream& os) const {
|
|
|
275
285
|
const uint8_t family_id = FAMILY_ID;
|
|
276
286
|
const uint8_t flags_byte = (is_empty() ? 1 << flags::IS_EMPTY : 0);
|
|
277
287
|
const uint32_t unused32 = NULL_32;
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
288
|
+
write_count_min_value(sink, bytes_written, preamble_longs);
|
|
289
|
+
write_count_min_value(sink, bytes_written, ser_ver);
|
|
290
|
+
write_count_min_value(sink, bytes_written, family_id);
|
|
291
|
+
write_count_min_value(sink, bytes_written, flags_byte);
|
|
292
|
+
write_count_min_value(sink, bytes_written, unused32);
|
|
283
293
|
|
|
284
294
|
// Long 1
|
|
285
295
|
const uint32_t nbuckets = _num_buckets;
|
|
286
296
|
const uint8_t nhashes = _num_hashes;
|
|
287
297
|
const uint16_t seed_hash(compute_seed_hash(_seed));
|
|
288
298
|
const uint8_t unused8 = NULL_8;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (is_empty()) return; // sketch is empty, no need to write further bytes.
|
|
299
|
+
write_count_min_value(sink, bytes_written, nbuckets);
|
|
300
|
+
write_count_min_value(sink, bytes_written, nhashes);
|
|
301
|
+
write_count_min_value(sink, bytes_written, seed_hash);
|
|
302
|
+
write_count_min_value(sink, bytes_written, unused8);
|
|
303
|
+
if (is_empty()) { return bytes_written; } // sketch is empty, no need to write further bytes.
|
|
294
304
|
|
|
295
305
|
// Long 2
|
|
296
|
-
|
|
306
|
+
const W t_weight = _total_weight;
|
|
307
|
+
write_count_min_value(sink, bytes_written, t_weight);
|
|
297
308
|
|
|
298
309
|
// Long 3 onwards: remaining bytes are consumed by writing the weight and the array values.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
310
|
+
const size_t sketch_array_bytes = sizeof(W) * _sketch_array.size();
|
|
311
|
+
if (sketch_array_bytes > 0) {
|
|
312
|
+
sink(_sketch_array.data(), sketch_array_bytes);
|
|
313
|
+
bytes_written += sketch_array_bytes;
|
|
303
314
|
}
|
|
315
|
+
|
|
316
|
+
return bytes_written;
|
|
304
317
|
}
|
|
305
318
|
|
|
306
319
|
template<typename W, typename A>
|
|
@@ -327,7 +340,7 @@ auto count_min_sketch<W,A>::deserialize(std::istream& is, uint64_t seed, const A
|
|
|
327
340
|
}
|
|
328
341
|
count_min_sketch c(nhashes, nbuckets, seed, allocator);
|
|
329
342
|
const bool is_empty = (flags_byte & (1 << flags::IS_EMPTY)) > 0;
|
|
330
|
-
if (is_empty == 1) return c; // sketch is empty, no need to read further.
|
|
343
|
+
if (is_empty == 1) { return c; } // sketch is empty, no need to read further.
|
|
331
344
|
|
|
332
345
|
// Set the sketch weight and read in the sketch values
|
|
333
346
|
const auto weight = read<W>(is);
|
|
@@ -351,40 +364,9 @@ template<typename W, typename A>
|
|
|
351
364
|
auto count_min_sketch<W,A>::serialize(unsigned header_size_bytes) const -> vector_bytes {
|
|
352
365
|
vector_bytes bytes(header_size_bytes + get_serialized_size_bytes(), 0, _allocator);
|
|
353
366
|
uint8_t *ptr = bytes.data() + header_size_bytes;
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
ptr += copy_to_mem(preamble_longs, ptr);
|
|
358
|
-
const uint8_t ser_ver = SERIAL_VERSION_1;
|
|
359
|
-
ptr += copy_to_mem(ser_ver, ptr);
|
|
360
|
-
const uint8_t family_id = FAMILY_ID;
|
|
361
|
-
ptr += copy_to_mem(family_id, ptr);
|
|
362
|
-
const uint8_t flags_byte = (is_empty() ? 1 << flags::IS_EMPTY : 0);
|
|
363
|
-
ptr += copy_to_mem(flags_byte, ptr);
|
|
364
|
-
const uint32_t unused32 = NULL_32;
|
|
365
|
-
ptr += copy_to_mem(unused32, ptr);
|
|
366
|
-
|
|
367
|
-
// Long 1
|
|
368
|
-
const uint32_t nbuckets = _num_buckets;
|
|
369
|
-
const uint8_t nhashes = _num_hashes;
|
|
370
|
-
const uint16_t seed_hash(compute_seed_hash(_seed));
|
|
371
|
-
const uint8_t null_characters_8 = NULL_8;
|
|
372
|
-
ptr += copy_to_mem(nbuckets, ptr);
|
|
373
|
-
ptr += copy_to_mem(nhashes, ptr);
|
|
374
|
-
ptr += copy_to_mem(seed_hash, ptr);
|
|
375
|
-
ptr += copy_to_mem(null_characters_8, ptr);
|
|
376
|
-
if (is_empty()) return bytes; // sketch is empty, no need to write further bytes.
|
|
377
|
-
|
|
378
|
-
// Long 2
|
|
379
|
-
const W t_weight = _total_weight;
|
|
380
|
-
ptr += copy_to_mem(t_weight, ptr);
|
|
381
|
-
|
|
382
|
-
// Long 3 onwards: remaining bytes are consumed by writing the weight and the array values.
|
|
383
|
-
auto it = _sketch_array.begin();
|
|
384
|
-
while (it != _sketch_array.end()) {
|
|
385
|
-
ptr += copy_to_mem(*it, ptr);
|
|
386
|
-
++it;
|
|
387
|
-
}
|
|
367
|
+
serialize_to([&ptr](const void* data, size_t size) {
|
|
368
|
+
ptr += copy_to_mem(data, ptr, size);
|
|
369
|
+
});
|
|
388
370
|
|
|
389
371
|
return bytes;
|
|
390
372
|
}
|
|
@@ -423,9 +405,10 @@ auto count_min_sketch<W,A>::deserialize(const void* bytes, size_t size, uint64_t
|
|
|
423
405
|
}
|
|
424
406
|
count_min_sketch c(nhashes, nbuckets, seed, allocator);
|
|
425
407
|
const bool is_empty = (flags_byte & (1 << flags::IS_EMPTY)) > 0;
|
|
426
|
-
if (is_empty) return c; // sketch is empty, no need to read further.
|
|
408
|
+
if (is_empty) { return c; } // sketch is empty, no need to read further.
|
|
427
409
|
|
|
428
|
-
|
|
410
|
+
// preamble (already read) + total weight + table; nbuckets * nhashes < 2^30 was checked by the constructor
|
|
411
|
+
ensure_minimum_memory(size, PREAMBLE_LONGS_SHORT * sizeof(uint64_t) + sizeof(W) * (1 + c._sketch_array.size()));
|
|
429
412
|
|
|
430
413
|
// Long 2 is the weight.
|
|
431
414
|
W weight;
|
|
@@ -449,8 +432,7 @@ string<A> count_min_sketch<W,A>::to_string() const {
|
|
|
449
432
|
// count the number of used entries in the sketch
|
|
450
433
|
uint64_t num_nonzero = 0;
|
|
451
434
|
for (const auto entry: _sketch_array) {
|
|
452
|
-
if (entry != static_cast<W>(0.0))
|
|
453
|
-
++num_nonzero;
|
|
435
|
+
if (entry != static_cast<W>(0.0)) { ++num_nonzero; }
|
|
454
436
|
}
|
|
455
437
|
|
|
456
438
|
// Using a temporary stream for implementation here does not comply with AllocatorAwareContainer requirements.
|
|
@@ -55,7 +55,7 @@ TEST_CASE("CM init") {
|
|
|
55
55
|
TEST_CASE("CM parameter suggestions", "[error parameters]") {
|
|
56
56
|
|
|
57
57
|
// Bucket suggestions
|
|
58
|
-
|
|
58
|
+
REQUIRE_THROWS_WITH(count_min_sketch<uint64_t>::suggest_num_buckets(-1.0), "Relative error must be at least 0.");
|
|
59
59
|
REQUIRE(count_min_sketch<uint64_t>::suggest_num_buckets(0.2) == 14);
|
|
60
60
|
REQUIRE(count_min_sketch<uint64_t>::suggest_num_buckets(0.1) == 28);
|
|
61
61
|
REQUIRE(count_min_sketch<uint64_t>::suggest_num_buckets(0.05) == 55);
|
|
@@ -69,8 +69,8 @@ TEST_CASE("CM parameter suggestions", "[error parameters]") {
|
|
|
69
69
|
REQUIRE(count_min_sketch<uint64_t>(n_hashes, 272).get_relative_error() <= 0.01);
|
|
70
70
|
|
|
71
71
|
// Hash suggestions
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
REQUIRE_THROWS_WITH(count_min_sketch<uint64_t>::suggest_num_hashes(10.0), "Confidence must be between 0 and 1.0 (inclusive)." );
|
|
73
|
+
REQUIRE_THROWS_WITH(count_min_sketch<uint64_t>::suggest_num_hashes(-1.0), "Confidence must be between 0 and 1.0 (inclusive)." );
|
|
74
74
|
REQUIRE(count_min_sketch<uint64_t>::suggest_num_hashes(0.682689492) == 2); // 1 STDDEV
|
|
75
75
|
REQUIRE(count_min_sketch<uint64_t>::suggest_num_hashes(0.954499736) == 4); // 2 STDDEV
|
|
76
76
|
REQUIRE(count_min_sketch<uint64_t>::suggest_num_hashes(0.997300204) == 6); // 3 STDDEV
|
|
@@ -161,9 +161,9 @@ TEST_CASE("CM merge - reject", "[reject cases]") {
|
|
|
161
161
|
std::vector<count_min_sketch<uint64_t>> sketches = {s1, s2, s3};
|
|
162
162
|
|
|
163
163
|
// Fail cases
|
|
164
|
-
|
|
164
|
+
REQUIRE_THROWS_WITH(s.merge(s), "Cannot merge a sketch with itself." );
|
|
165
165
|
for (count_min_sketch<uint64_t> sk : sketches) {
|
|
166
|
-
|
|
166
|
+
REQUIRE_THROWS_WITH(s.merge(sk), "Incompatible sketch configuration." );
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
@@ -215,7 +215,7 @@ TEST_CASE("CountMin sketch: serialize-deserialize empty", "[cm_sketch]") {
|
|
|
215
215
|
for (auto di: d) {
|
|
216
216
|
REQUIRE(di == 0);
|
|
217
217
|
}
|
|
218
|
-
std::ofstream os("
|
|
218
|
+
std::ofstream os("count_min_empty_cpp.sk", std::ios::binary);
|
|
219
219
|
c.serialize(os);
|
|
220
220
|
}
|
|
221
221
|
|
|
@@ -243,7 +243,7 @@ TEST_CASE("CountMin sketch: serialize-deserialize non-empty", "[cm_sketch]") {
|
|
|
243
243
|
++d_it;
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
std::ofstream os("
|
|
246
|
+
std::ofstream os("count_min_non_empty_cpp.sk", std::ios::binary);
|
|
247
247
|
c.serialize(os);
|
|
248
248
|
}
|
|
249
249
|
|
|
@@ -300,4 +300,60 @@ TEST_CASE("CountMin sketch: bytes serialize-deserialize non-empty", "[cm_sketch]
|
|
|
300
300
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
TEST_CASE("CountMin sketch: sink serialize-deserialize round trip", "[cm_sketch]") {
|
|
304
|
+
auto check_sink_serialize = [](const count_min_sketch<uint64_t>& c) {
|
|
305
|
+
std::vector<uint8_t> sink_bytes;
|
|
306
|
+
sink_bytes.reserve(c.get_serialized_size_bytes());
|
|
307
|
+
|
|
308
|
+
const size_t bytes_written = c.serialize_to([&sink_bytes](const void* data, size_t size) {
|
|
309
|
+
const auto* begin = static_cast<const uint8_t*>(data);
|
|
310
|
+
sink_bytes.insert(sink_bytes.end(), begin, begin + size);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
REQUIRE(bytes_written == c.get_serialized_size_bytes());
|
|
314
|
+
REQUIRE(sink_bytes.size() == bytes_written);
|
|
315
|
+
|
|
316
|
+
auto d = count_min_sketch<uint64_t>::deserialize(sink_bytes.data(), sink_bytes.size(), c.get_seed());
|
|
317
|
+
REQUIRE(c.get_num_hashes() == d.get_num_hashes());
|
|
318
|
+
REQUIRE(c.get_num_buckets() == d.get_num_buckets());
|
|
319
|
+
REQUIRE(c.get_seed() == d.get_seed());
|
|
320
|
+
REQUIRE(c.get_total_weight() == d.get_total_weight());
|
|
321
|
+
|
|
322
|
+
auto c_it = c.begin();
|
|
323
|
+
auto d_it = d.begin();
|
|
324
|
+
while (c_it != c.end()) {
|
|
325
|
+
REQUIRE(*c_it == *d_it);
|
|
326
|
+
++c_it;
|
|
327
|
+
++d_it;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
count_min_sketch<uint64_t> empty(3, 32);
|
|
332
|
+
check_sink_serialize(empty);
|
|
333
|
+
|
|
334
|
+
count_min_sketch<uint64_t> non_empty(5, 64);
|
|
335
|
+
for (uint64_t i=0; i < 10; ++i) non_empty.update(i, 10 * i * i);
|
|
336
|
+
check_sink_serialize(non_empty);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
TEST_CASE("CountMin sketch: bytes deserialize truncated non-empty", "[cm_sketch]") {
|
|
340
|
+
count_min_sketch<uint64_t> c(5, 64);
|
|
341
|
+
for (uint64_t i = 0; i < 10; ++i) c.update(i, 10 * i * i);
|
|
342
|
+
auto bytes = c.serialize();
|
|
343
|
+
for (size_t trim = 1; trim <= 24; ++trim) {
|
|
344
|
+
REQUIRE_THROWS_AS(count_min_sketch<uint64_t>::deserialize(bytes.data(), bytes.size() - trim), std::out_of_range);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
TEST_CASE("CountMin sketch: deserialize rejects overflowing dimensions", "[cm_sketch]") {
|
|
349
|
+
// num_buckets * num_hashes wraps to 0 in 32-bit arithmetic
|
|
350
|
+
count_min_sketch<uint64_t> c(2, 64);
|
|
351
|
+
c.update(uint64_t(1));
|
|
352
|
+
auto bytes = c.serialize();
|
|
353
|
+
const uint32_t num_buckets = 1U << 31;
|
|
354
|
+
std::memcpy(bytes.data() + 8, &num_buckets, sizeof(num_buckets));
|
|
355
|
+
REQUIRE_THROWS_AS(count_min_sketch<uint64_t>::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
|
|
356
|
+
REQUIRE_THROWS_AS(count_min_sketch<uint64_t>(2, num_buckets), std::invalid_argument);
|
|
357
|
+
}
|
|
358
|
+
|
|
303
359
|
} /* namespace datasketches */
|
|
@@ -157,7 +157,7 @@ void cpc_compressor<A>::compress(const cpc_sketch_alloc<A>& source, compressed_s
|
|
|
157
157
|
break;
|
|
158
158
|
case cpc_sketch_alloc<A>::flavor::PINNED:
|
|
159
159
|
compress_pinned_flavor(source, result);
|
|
160
|
-
if (result.window_data.size() == 0) throw std::logic_error("window is
|
|
160
|
+
if (result.window_data.size() == 0) throw std::logic_error("window is expected");
|
|
161
161
|
break;
|
|
162
162
|
case cpc_sketch_alloc<A>::flavor::SLIDING:
|
|
163
163
|
compress_sliding_flavor(source, result);
|
|
@@ -354,6 +354,7 @@ void cpc_compressor<A>::uncompress_sliding_flavor(const compressed_state<A>& sou
|
|
|
354
354
|
const uint32_t row_col = pairs[i];
|
|
355
355
|
const uint32_t row = row_col >> 6;
|
|
356
356
|
uint8_t col = row_col & 63;
|
|
357
|
+
if (col >= 56) throw std::out_of_range("col out of range");
|
|
357
358
|
// first undo the permutation
|
|
358
359
|
col = permutation[col];
|
|
359
360
|
// then undo the rotation: old = (new + (offset+8)) mod 64
|
|
@@ -390,6 +391,9 @@ auto cpc_compressor<A>::uncompress_surprising_values(const uint32_t* data, uint3
|
|
|
390
391
|
vector_u32 pairs(num_pairs, 0, allocator);
|
|
391
392
|
const uint8_t num_base_bits = golomb_choose_number_of_base_bits(k + num_pairs, num_pairs);
|
|
392
393
|
low_level_uncompress_pairs(pairs.data(), num_pairs, num_base_bits, data, data_words);
|
|
394
|
+
for (uint32_t i = 0; i < num_pairs; i++) {
|
|
395
|
+
if ((pairs[i] >> 6) >= k) throw std::out_of_range("row index out of range");
|
|
396
|
+
}
|
|
393
397
|
return pairs;
|
|
394
398
|
}
|
|
395
399
|
|
|
@@ -472,8 +476,10 @@ static inline void maybe_flush_bitbuf(uint64_t& bitbuf, uint8_t& bufbits, uint32
|
|
|
472
476
|
}
|
|
473
477
|
}
|
|
474
478
|
|
|
475
|
-
static inline void maybe_fill_bitbuf(uint64_t& bitbuf, uint8_t& bufbits, const uint32_t* wordarr, uint32_t& wordindex,
|
|
479
|
+
static inline void maybe_fill_bitbuf(uint64_t& bitbuf, uint8_t& bufbits, const uint32_t* wordarr, uint32_t& wordindex,
|
|
480
|
+
uint32_t numwords, uint8_t minbits) {
|
|
476
481
|
if (bufbits < minbits) {
|
|
482
|
+
if (wordindex >= numwords) throw std::out_of_range("compressed data over-run");
|
|
477
483
|
bitbuf |= static_cast<uint64_t>(wordarr[wordindex++]) << bufbits;
|
|
478
484
|
bufbits += 32;
|
|
479
485
|
}
|
|
@@ -530,7 +536,7 @@ void cpc_compressor<A>::low_level_uncompress_bytes(
|
|
|
530
536
|
if (compressed_words == nullptr) throw std::logic_error("compressed_words == NULL");
|
|
531
537
|
|
|
532
538
|
for (uint32_t byte_index = 0; byte_index < num_bytes_to_decode; byte_index++) {
|
|
533
|
-
maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, 12); // ensure 12 bits in bit buffer
|
|
539
|
+
maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, num_compressed_words, 12); // ensure 12 bits in bit buffer
|
|
534
540
|
|
|
535
541
|
const size_t peek12 = bitbuf & 0xfff; // These 12 bits will include an entire Huffman codeword.
|
|
536
542
|
const uint16_t lookup = decoding_table[peek12];
|
|
@@ -547,6 +553,7 @@ void cpc_compressor<A>::low_level_uncompress_bytes(
|
|
|
547
553
|
|
|
548
554
|
static inline uint64_t read_unary(
|
|
549
555
|
const uint32_t* compressed_words,
|
|
556
|
+
uint32_t num_compressed_words,
|
|
550
557
|
uint32_t& next_word_index,
|
|
551
558
|
uint64_t& bitbuf,
|
|
552
559
|
uint8_t& bufbits
|
|
@@ -646,7 +653,7 @@ void cpc_compressor<A>::low_level_uncompress_pairs(
|
|
|
646
653
|
// y_delta_lo (basebits)
|
|
647
654
|
|
|
648
655
|
for (uint32_t pair_index = 0; pair_index < num_pairs_to_decode; pair_index++) {
|
|
649
|
-
maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, 12); // ensure 12 bits in bit buffer
|
|
656
|
+
maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, num_compressed_words, 12); // ensure 12 bits in bit buffer
|
|
650
657
|
const size_t peek12 = bitbuf & 0xfff;
|
|
651
658
|
const uint16_t lookup = length_limited_unary_decoding_table65[peek12];
|
|
652
659
|
const uint8_t code_word_length = lookup >> 8;
|
|
@@ -654,9 +661,9 @@ void cpc_compressor<A>::low_level_uncompress_pairs(
|
|
|
654
661
|
bitbuf >>= code_word_length;
|
|
655
662
|
bufbits -= code_word_length;
|
|
656
663
|
|
|
657
|
-
const uint64_t golomb_hi = read_unary(compressed_words, word_index, bitbuf, bufbits);
|
|
664
|
+
const uint64_t golomb_hi = read_unary(compressed_words, num_compressed_words, word_index, bitbuf, bufbits);
|
|
658
665
|
|
|
659
|
-
maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, num_base_bits); // ensure num_base_bits in bit buffer
|
|
666
|
+
maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, num_compressed_words, num_base_bits); // ensure num_base_bits in bit buffer
|
|
660
667
|
const uint64_t golomb_lo = bitbuf & golomb_lo_mask;
|
|
661
668
|
bitbuf >>= num_base_bits;
|
|
662
669
|
bufbits -= num_base_bits;
|
|
@@ -666,6 +673,7 @@ void cpc_compressor<A>::low_level_uncompress_pairs(
|
|
|
666
673
|
if (y_delta > 0) predicted_col_index = 0;
|
|
667
674
|
const uint32_t row_index = static_cast<uint32_t>(predicted_row_index + y_delta);
|
|
668
675
|
const uint8_t col_index = predicted_col_index + x_delta;
|
|
676
|
+
if (col_index > 63) throw std::out_of_range("column index out of range");
|
|
669
677
|
const uint32_t row_col = (row_index << 6) | col_index;
|
|
670
678
|
pair_array[pair_index] = row_col;
|
|
671
679
|
predicted_row_index = row_index;
|
|
@@ -676,6 +684,7 @@ void cpc_compressor<A>::low_level_uncompress_pairs(
|
|
|
676
684
|
|
|
677
685
|
uint64_t read_unary(
|
|
678
686
|
const uint32_t* compressed_words,
|
|
687
|
+
uint32_t num_compressed_words,
|
|
679
688
|
uint32_t& next_word_index,
|
|
680
689
|
uint64_t& bitbuf,
|
|
681
690
|
uint8_t& bufbits
|
|
@@ -683,7 +692,7 @@ uint64_t read_unary(
|
|
|
683
692
|
if (compressed_words == nullptr) throw std::logic_error("compressed_words == NULL");
|
|
684
693
|
size_t subtotal = 0;
|
|
685
694
|
while (true) {
|
|
686
|
-
maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, next_word_index, 8); // ensure 8 bits in bit buffer
|
|
695
|
+
maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, next_word_index, num_compressed_words, 8); // ensure 8 bits in bit buffer
|
|
687
696
|
|
|
688
697
|
const uint8_t peek8 = bitbuf & 0xff; // These 8 bits include either all or part of the Unary codeword
|
|
689
698
|
const uint8_t trailing_zeros = byte_trailing_zeros_table[peek8];
|
|
@@ -315,6 +315,7 @@ private:
|
|
|
315
315
|
inline size_t copy_hip_to_mem(void* dst) const;
|
|
316
316
|
|
|
317
317
|
static void check_lg_k(uint8_t lg_k);
|
|
318
|
+
static void check_num_coupons(uint8_t lg_k, uint32_t num_coupons, uint32_t num_pairs);
|
|
318
319
|
|
|
319
320
|
friend cpc_compressor<A>;
|
|
320
321
|
friend cpc_union_alloc<A>;
|