1#[cfg(not(any(feature = "std", test)))]
10use crate::math::FloatExt;
11use crate::math::{f64_i32, f64_u64, hash_index, i64_f64, ipow64, u64_f64, usize_f64};
12use alice_det_math::{exp64, ln64};
13use core::hash::{Hash, Hasher};
14
15const POW2_NEG_LUT: [f64; 65] = [
22 1.0, 0.5, 0.25, 0.125, 0.0625, 0.031_25, 0.015_625, 0.007_812_5, 0.003_906_25, 0.001_953_125, 0.000_976_562_5, 0.000_488_281_25, 0.000_244_140_625, 0.000_122_070_312_5, 6.103_515_625e-5, 3.051_757_812_5e-5, 1.525_878_906_25e-5, 7.629_394_531_25e-6, 3.814_697_265_625e-6, 1.907_348_632_812_5e-6, 9.536_743_164_062_5e-7, 4.768_371_582_031_25e-7, 2.384_185_791_015_625e-7, 1.192_092_895_507_812_5e-7, 5.960_464_477_539_063e-8, 2.980_232_238_769_531_2e-8, 1.490_116_119_384_765_6e-8, 7.450_580_596_923_828e-9, 3.725_290_298_461_914e-9, 1.862_645_149_230_957e-9, 9.313_225_746_154_785e-10, 4.656_612_873_077_393e-10, 2.328_306_436_538_696_3e-10, 1.164_153_218_269_348_1e-10, 5.820_766_091_346_741e-11, 2.910_383_045_673_370_4e-11, 1.455_191_522_836_685_2e-11, 7.275_957_614_183_426e-12, 3.637_978_807_091_713e-12, 1.818_989_403_545_856_5e-12, 9.094_947_017_729_282e-13, 4.547_473_508_864_641e-13, 2.273_736_754_432_320_6e-13, 1.136_868_377_216_160_3e-13, 5.684_341_886_080_802e-14, 2.842_170_943_040_401e-14, 1.421_085_471_520_200_4e-14, 7.105_427_357_601_002e-15, 3.552_713_678_800_501e-15, 1.776_356_839_400_250_5e-15, 8.881_784_197_001_252e-16, 4.440_892_098_500_626e-16, 2.220_446_049_250_313e-16, 1.110_223_024_625_156_5e-16, 5.551_115_123_125_783e-17, 2.775_557_561_562_891_4e-17, 1.387_778_780_781_445_7e-17, 6.938_893_903_907_228e-18, 3.469_446_951_953_614e-18, 1.734_723_475_976_807e-18, 8.673_617_379_884_035e-19, 4.336_808_689_942_018e-19, 2.168_404_344_971_009e-19, 1.084_202_172_485_504_4e-19, 5.421_010_862_427_522e-20, ];
88
89const RCP_POW2_52: f64 = 1.0 / 4_503_599_627_370_496.0; #[inline]
95fn fast_log2_approx(x: f64) -> f64 {
96 let bits = x.to_bits();
99 #[allow(clippy::cast_possible_wrap)]
101 let exponent = ((bits >> 52) & 0x7FF) as i64;
102 let mantissa = bits & 0x000F_FFFF_FFFF_FFFF;
103
104 let int_part = exponent - 1023;
107 let frac_part = u64_f64(mantissa) * RCP_POW2_52;
108
109 i64_f64(int_part) + frac_part
110}
111
112pub trait Mergeable {
118 fn merge(&mut self, other: &Self);
120}
121
122#[derive(Clone, Copy, Debug)]
128pub struct FnvHasher {
129 state: u64,
130}
131
132impl FnvHasher {
133 const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
134 const FNV_PRIME: u64 = 0x0100_0000_01b3;
135
136 #[inline]
137 #[must_use]
138 pub const fn new() -> Self {
139 Self {
140 state: Self::FNV_OFFSET,
141 }
142 }
143
144 #[inline]
147 const fn mix(mut h: u64) -> u64 {
148 h ^= h >> 33;
149 h = h.wrapping_mul(0xff51_afd7_ed55_8ccd);
150 h ^= h >> 33;
151 h = h.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
152 h ^= h >> 33;
153 h
154 }
155
156 #[inline]
157 #[must_use]
158 pub fn hash_bytes(data: &[u8]) -> u64 {
159 let mut hasher = Self::new();
160 hasher.write(data);
161 Self::mix(hasher.state)
162 }
163
164 #[inline]
165 #[must_use]
166 pub fn hash_u64(value: u64) -> u64 {
167 Self::hash_bytes(&value.to_le_bytes())
168 }
169
170 #[inline]
171 #[must_use]
172 pub fn hash_u128(value: u128) -> u64 {
173 Self::hash_bytes(&value.to_le_bytes())
174 }
175}
176
177impl Default for FnvHasher {
178 fn default() -> Self {
179 Self::new()
180 }
181}
182
183impl Hasher for FnvHasher {
184 #[inline]
185 fn write(&mut self, bytes: &[u8]) {
186 for &byte in bytes {
187 self.state ^= u64::from(byte);
188 self.state = self.state.wrapping_mul(Self::FNV_PRIME);
189 }
190 }
191
192 #[inline]
193 fn finish(&self) -> u64 {
194 Self::mix(self.state)
196 }
197}
198
199macro_rules! impl_hyperloglog {
205 ($name:ident, $p:expr, $m:expr) => {
206 #[derive(Clone, Debug)]
211 pub struct $name {
212 registers: [u8; $m],
214 }
215
216 impl $name {
217 pub const M: usize = $m;
219 pub const P: usize = $p;
221
222 const ALPHA: f64 = 0.7213 / (1.0 + 1.079 / ($m as f64));
224
225 #[inline]
227 pub const fn new() -> Self {
228 #[allow(clippy::large_stack_arrays)]
229 Self {
230 registers: [0u8; $m],
231 }
232 }
233
234 #[inline]
236 pub const fn insert_hash(&mut self, hash: u64) {
237 let idx = hash_index(hash) & (Self::M - 1);
238 let w = hash >> $p;
239 let rho = if w == 0 {
242 #[allow(clippy::cast_possible_truncation)]
244 let rho = (64 - $p + 1) as u8;
245 rho
246 } else {
247 #[allow(clippy::cast_possible_truncation)]
248 let rho = (w.leading_zeros() as usize - $p + 1) as u8;
249 rho
250 };
251 if rho > self.registers[idx] {
252 self.registers[idx] = rho;
253 }
254 }
255
256 #[inline]
258 pub fn insert<T: Hash>(&mut self, value: &T) {
259 let mut hasher = FnvHasher::new();
260 value.hash(&mut hasher);
261 self.insert_hash(hasher.finish());
262 }
263
264 #[inline]
266 pub fn insert_bytes(&mut self, bytes: &[u8]) {
267 self.insert_hash(FnvHasher::hash_bytes(bytes));
268 }
269
270 pub fn cardinality(&self) -> f64 {
273 let mut sum = 0.0f64;
274 let mut zeros = 0usize;
275
276 for ® in &self.registers {
278 let idx = (reg as usize).min(64);
280 sum += POW2_NEG_LUT[idx];
281 if reg == 0 {
282 zeros += 1;
283 }
284 }
285
286 let m = usize_f64(Self::M);
287 let inv_sum = 1.0 / sum;
288 let raw_estimate = Self::ALPHA * m * m * inv_sum;
289
290 if raw_estimate <= 2.5 * m && zeros > 0 {
291 let inv_zeros = 1.0 / usize_f64(zeros);
292 m * ln64(m * inv_zeros)
293 } else {
294 raw_estimate
295 }
296 }
297
298 #[inline]
300 pub const fn registers(&self) -> &[u8] {
301 &self.registers
302 }
303
304 #[inline]
306 pub const fn clear(&mut self) {
307 #[allow(clippy::large_stack_arrays)]
308 {
309 self.registers = [0u8; $m];
310 }
311 }
312 }
313
314 impl Default for $name {
315 fn default() -> Self {
316 Self::new()
317 }
318 }
319
320 impl Mergeable for $name {
321 fn merge(&mut self, other: &Self) {
322 for (dst, &src) in self.registers.iter_mut().zip(other.registers.iter()) {
323 if src > *dst {
324 *dst = src;
325 }
326 }
327 }
328 }
329 };
330}
331
332impl_hyperloglog!(HyperLogLog10, 10, 1024); impl_hyperloglog!(HyperLogLog12, 12, 4096); impl_hyperloglog!(HyperLogLog14, 14, 16384); impl_hyperloglog!(HyperLogLog16, 16, 65536); pub type HyperLogLog = HyperLogLog14;
340
341macro_rules! impl_ddsketch {
367 ($name:ident, $bins:expr) => {
368 #[derive(Clone, Debug)]
369 pub struct $name {
370 positive_bins: [u64; $bins],
371 negative_bins: [u64; $bins],
372 zero_count: u64,
373 count: u64,
374 non_finite: u64,
375 min: f64,
376 max: f64,
377 sum: f64,
378 gamma: f64,
379 ln_gamma: f64,
380 inv_ln_gamma: f64,
381 alpha: f64,
382 offset: i32,
383 }
384
385 impl $name {
386 pub const BINS: usize = $bins;
387
388 pub fn new(alpha: f64) -> Self {
389 let gamma = (1.0 + alpha) / (1.0 - alpha);
390 let ln_gamma = ln64(gamma);
391 let offset = ($bins / 4) as i32;
395
396 Self {
397 positive_bins: [0u64; $bins],
398 negative_bins: [0u64; $bins],
399 zero_count: 0,
400 count: 0,
401 non_finite: 0,
402 min: f64::INFINITY,
403 max: f64::NEG_INFINITY,
404 sum: 0.0,
405 gamma,
406 ln_gamma,
407 inv_ln_gamma: 1.0 / ln_gamma,
408 alpha,
409 offset,
410 }
411 }
412
413 #[inline]
438 pub fn insert(&mut self, value: f64) {
439 if !value.is_finite() {
440 self.non_finite += 1;
441 return;
442 }
443
444 self.count += 1;
445 self.sum += value;
446
447 if value < self.min {
448 self.min = value;
449 }
450 if value > self.max {
451 self.max = value;
452 }
453
454 if value > 0.0 {
459 let idx = self.bucket_index(value).min($bins - 1);
460 self.positive_bins[idx] += 1;
461 } else if value < 0.0 {
462 let idx = self.bucket_index(-value).min($bins - 1);
463 self.negative_bins[idx] += 1;
464 } else {
465 self.zero_count += 1;
466 }
467 }
468
469 #[inline]
487 fn bucket_index(&self, value: f64) -> usize {
488 let idx =
489 f64_i32((ln64(value) * self.inv_ln_gamma).ceil()).saturating_add(self.offset);
490 usize::try_from(idx.max(0)).unwrap_or(0)
491 }
492
493 #[inline(always)]
496 #[allow(dead_code)]
497 fn bucket_index_fast(&self, value: f64) -> usize {
498 let log2_gamma = self.ln_gamma / core::f64::consts::LN_2;
499 let inv_log2_gamma = 1.0 / log2_gamma;
500 let log2_value = fast_log2_approx(value);
501 let idx = f64_i32((log2_value * inv_log2_gamma).ceil()).saturating_add(self.offset);
502 usize::try_from(idx.max(0)).unwrap_or(0)
503 }
504
505 #[inline]
515 fn bucket_representative(&self, idx: usize) -> f64 {
516 let exp = i32::try_from(idx).unwrap_or(i32::MAX) - self.offset;
517 2.0 * ipow64(self.gamma, exp) / (self.gamma + 1.0)
518 }
519
520 pub fn quantile(&self, q: f64) -> f64 {
521 if self.count == 0 {
522 return 0.0;
523 }
524 let rank = f64_u64((q * u64_f64(self.count)).ceil()).max(1);
525 let clamp = |v: f64| v.clamp(self.min, self.max);
528 let mut cumulative = 0u64;
529 for (idx, &count) in self.negative_bins.iter().enumerate().rev() {
534 cumulative += count;
535 if cumulative >= rank {
536 return clamp(-self.bucket_representative(idx));
537 }
538 }
539 cumulative += self.zero_count;
540 if cumulative >= rank {
541 return 0.0;
542 }
543 for (idx, &count) in self.positive_bins.iter().enumerate() {
544 cumulative += count;
545 if cumulative >= rank {
546 return clamp(self.bucket_representative(idx));
547 }
548 }
549 self.max
550 }
551 #[inline]
556 pub const fn count(&self) -> u64 {
557 self.count
558 }
559
560 #[inline]
568 pub const fn non_finite(&self) -> u64 {
569 self.non_finite
570 }
571
572 #[inline]
576 pub const fn sum(&self) -> f64 {
577 self.sum
578 }
579
580 #[inline(always)]
583 pub fn mean(&self) -> f64 {
584 if self.count == 0 {
585 0.0
586 } else {
587 self.sum * (1.0 / u64_f64(self.count))
588 }
589 }
590
591 #[inline]
594 pub const fn min(&self) -> f64 {
595 self.min
596 }
597
598 #[inline]
601 pub const fn max(&self) -> f64 {
602 self.max
603 }
604
605 #[inline]
606 pub const fn alpha(&self) -> f64 {
607 self.alpha
608 }
609
610 #[must_use]
618 pub fn accurate_range(&self) -> (f64, f64) {
619 let lowest = -self.offset;
620 let highest = i32::try_from($bins - 1).unwrap_or(i32::MAX) - self.offset;
621 (ipow64(self.gamma, lowest), ipow64(self.gamma, highest))
622 }
623
624 pub const fn clear(&mut self) {
625 self.positive_bins = [0u64; $bins];
626 self.negative_bins = [0u64; $bins];
627 self.zero_count = 0;
628 self.count = 0;
629 self.non_finite = 0;
630 self.min = f64::INFINITY;
631 self.max = f64::NEG_INFINITY;
632 self.sum = 0.0;
633 }
634 }
635
636 impl Mergeable for $name {
637 fn merge(&mut self, other: &Self) {
638 for (dst, &src) in self
639 .positive_bins
640 .iter_mut()
641 .zip(other.positive_bins.iter())
642 {
643 *dst += src;
644 }
645 for (dst, &src) in self
646 .negative_bins
647 .iter_mut()
648 .zip(other.negative_bins.iter())
649 {
650 *dst += src;
651 }
652 self.zero_count += other.zero_count;
653 self.count += other.count;
654 self.non_finite += other.non_finite;
655 self.sum += other.sum;
656
657 if other.min < self.min {
658 self.min = other.min;
659 }
660 if other.max > self.max {
661 self.max = other.max;
662 }
663 }
664 }
665 };
666}
667
668impl_ddsketch!(DDSketch128, 128); impl_ddsketch!(DDSketch256, 256); impl_ddsketch!(DDSketch512, 512); impl_ddsketch!(DDSketch1024, 1024); impl_ddsketch!(DDSketch2048, 2048); pub type DDSketch = DDSketch2048;
677
678macro_rules! impl_countmin {
684 ($name:ident, $w:expr, $d:expr) => {
685 #[derive(Clone, Debug)]
687 pub struct $name {
688 counters: [[u64; $w]; $d],
689 total: u64,
690 }
691
692 impl $name {
693 pub const WIDTH: usize = $w;
694 pub const DEPTH: usize = $d;
695
696 #[inline]
697 pub const fn new() -> Self {
698 #[allow(clippy::large_stack_arrays)]
699 Self {
700 counters: [[0u64; $w]; $d],
701 total: 0,
702 }
703 }
704
705 #[inline]
706 const fn hash_for_row(hash: u64, row: usize) -> usize {
707 let h = hash.wrapping_add((row as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15));
708 let mixed = h ^ (h >> 33);
709 let mixed = mixed.wrapping_mul(0xff51_afd7_ed55_8ccd);
710 let mixed = mixed ^ (mixed >> 33);
711 hash_index(mixed) & ($w - 1)
712 }
713
714 #[inline]
715 pub fn insert_hash(&mut self, hash: u64, count: u64) {
716 self.total += count;
717 for row in 0..$d {
718 let col = Self::hash_for_row(hash, row);
719 self.counters[row][col] = self.counters[row][col].saturating_add(count);
720 }
721 }
722
723 #[inline]
724 pub fn insert<T: Hash>(&mut self, item: &T) {
725 let mut hasher = FnvHasher::new();
726 item.hash(&mut hasher);
727 self.insert_hash(hasher.finish(), 1);
728 }
729
730 #[inline]
731 pub fn insert_bytes(&mut self, bytes: &[u8]) {
732 self.insert_hash(FnvHasher::hash_bytes(bytes), 1);
733 }
734
735 #[inline]
736 pub fn estimate_hash(&self, hash: u64) -> u64 {
737 let mut min_count = u64::MAX;
738 for row in 0..$d {
739 let col = Self::hash_for_row(hash, row);
740 min_count = min_count.min(self.counters[row][col]);
741 }
742 min_count
743 }
744
745 #[inline]
746 pub fn estimate<T: Hash>(&self, item: &T) -> u64 {
747 let mut hasher = FnvHasher::new();
748 item.hash(&mut hasher);
749 self.estimate_hash(hasher.finish())
750 }
751
752 #[inline]
753 pub fn estimate_bytes(&self, bytes: &[u8]) -> u64 {
754 self.estimate_hash(FnvHasher::hash_bytes(bytes))
755 }
756
757 #[inline]
758 pub const fn total(&self) -> u64 {
759 self.total
760 }
761
762 #[inline]
763 pub const fn clear(&mut self) {
764 #[allow(clippy::large_stack_arrays)]
765 {
766 self.counters = [[0u64; $w]; $d];
767 }
768 self.total = 0;
769 }
770
771 #[inline(always)]
772 pub fn error_bound(&self) -> f64 {
773 core::f64::consts::E * (1.0 / f64::from($w))
774 }
775
776 #[inline]
777 pub fn confidence(&self) -> f64 {
778 1.0 - exp64(-f64::from($d))
779 }
780 }
781
782 impl Default for $name {
783 fn default() -> Self {
784 Self::new()
785 }
786 }
787
788 impl Mergeable for $name {
789 fn merge(&mut self, other: &Self) {
790 self.total += other.total;
791 for row in 0..$d {
792 for col in 0..$w {
793 self.counters[row][col] =
794 self.counters[row][col].saturating_add(other.counters[row][col]);
795 }
796 }
797 }
798 }
799 };
800}
801
802impl_countmin!(CountMinSketch1024x5, 1024, 5);
804impl_countmin!(CountMinSketch2048x7, 2048, 7);
805impl_countmin!(CountMinSketch4096x5, 4096, 5);
806
807pub type CountMinSketch = CountMinSketch1024x5;
809
810#[derive(Clone, Copy, Debug)]
816pub struct HeavyHitterEntry {
817 pub hash: u64,
819 pub count: u64,
821}
822
823macro_rules! impl_heavy_hitters {
825 ($name:ident, $cms_name:ident, $k:expr) => {
826 #[derive(Clone, Debug)]
828 pub struct $name {
829 cms: $cms_name,
830 top_k: [HeavyHitterEntry; $k],
831 count: usize,
832 }
833
834 impl $name {
835 pub const K: usize = $k;
836
837 #[inline]
838 pub const fn new() -> Self {
839 Self {
840 cms: $cms_name::new(),
841 top_k: [HeavyHitterEntry { hash: 0, count: 0 }; $k],
842 count: 0,
843 }
844 }
845
846 pub fn insert_hash(&mut self, hash: u64) {
847 self.cms.insert_hash(hash, 1);
848 let estimated = self.cms.estimate_hash(hash);
849
850 let mut found_idx = None;
851 for i in 0..self.count {
852 if self.top_k[i].hash == hash {
853 found_idx = Some(i);
854 break;
855 }
856 }
857
858 if let Some(idx) = found_idx {
859 self.top_k[idx].count = estimated;
860 self.sort_top_k();
861 } else if self.count < $k {
862 self.top_k[self.count] = HeavyHitterEntry {
863 hash,
864 count: estimated,
865 };
866 self.count += 1;
867 self.sort_top_k();
868 } else if estimated > self.top_k[0].count {
869 self.top_k[0] = HeavyHitterEntry {
870 hash,
871 count: estimated,
872 };
873 self.sort_top_k();
874 }
875 }
876
877 fn sort_top_k(&mut self) {
878 for i in 1..self.count {
879 let entry = self.top_k[i];
880 let mut j = i;
881 while j > 0 && self.top_k[j - 1].count > entry.count {
882 self.top_k[j] = self.top_k[j - 1];
883 j -= 1;
884 }
885 self.top_k[j] = entry;
886 }
887 }
888
889 pub fn top(&self) -> impl Iterator<Item = &HeavyHitterEntry> {
890 self.top_k[..self.count].iter().rev()
891 }
892
893 #[inline]
894 pub const fn cms(&self) -> &$cms_name {
895 &self.cms
896 }
897
898 pub const fn clear(&mut self) {
899 self.cms.clear();
900 self.top_k = [HeavyHitterEntry { hash: 0, count: 0 }; $k];
901 self.count = 0;
902 }
903 }
904
905 impl Default for $name {
906 fn default() -> Self {
907 Self::new()
908 }
909 }
910 };
911}
912
913impl_heavy_hitters!(HeavyHitters10, CountMinSketch1024x5, 10);
915impl_heavy_hitters!(HeavyHitters20, CountMinSketch2048x7, 20);
916impl_heavy_hitters!(HeavyHitters5, CountMinSketch1024x5, 5);
917
918pub type HeavyHitters = HeavyHitters10;
920
921#[cfg(test)]
926#[allow(clippy::float_cmp)]
927mod tests {
928 use super::*;
929
930 #[test]
931 fn test_fnv_hash() {
932 let h1 = FnvHasher::hash_u64(12345);
933 let h2 = FnvHasher::hash_u64(12345);
934 let h3 = FnvHasher::hash_u64(12346);
935
936 assert_eq!(h1, h2);
937 assert_ne!(h1, h3);
938 }
939
940 #[test]
941 fn test_hyperloglog_basic() {
942 let mut hll = HyperLogLog16::new();
944
945 for i in 0..1000u64 {
947 hll.insert_hash(FnvHasher::hash_u64(i));
948 }
949
950 let estimate = hll.cardinality();
951 assert!(
953 estimate > 800.0 && estimate < 1200.0,
954 "estimate = {estimate}"
955 );
956 }
957
958 #[test]
959 fn test_hyperloglog_merge() {
960 let mut hll1 = HyperLogLog16::new();
961 let mut hll2 = HyperLogLog16::new();
962
963 for i in 0..500u64 {
964 hll1.insert_hash(FnvHasher::hash_u64(i));
965 }
966 for i in 500..1000u64 {
967 hll2.insert_hash(FnvHasher::hash_u64(i));
968 }
969
970 hll1.merge(&hll2);
971 let estimate = hll1.cardinality();
972 assert!(
973 estimate > 800.0 && estimate < 1200.0,
974 "estimate = {estimate}"
975 );
976 }
977
978 #[test]
979 fn test_hyperloglog_16_large() {
980 let mut hll = HyperLogLog16::new();
981
982 for i in 0..100_000_u64 {
984 hll.insert_hash(FnvHasher::hash_u64(i));
985 }
986
987 let estimate = hll.cardinality();
988 assert!(
990 estimate > 75000.0 && estimate < 125_000.0,
991 "estimate = {estimate}"
992 );
993 }
994
995 #[test]
996 fn test_ddsketch_basic() {
997 let mut sketch = DDSketch2048::new(0.01);
999
1000 let values = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0];
1001 for v in values {
1002 sketch.insert(v);
1003 }
1004
1005 assert_eq!(sketch.count(), 10);
1006 assert!((sketch.mean() - 55.0).abs() < 0.001);
1007
1008 let p50 = sketch.quantile(0.5);
1009 assert!(p50 > 40.0 && p50 < 70.0, "p50 = {p50}");
1010
1011 let p99 = sketch.quantile(0.99);
1012 assert!(p99 > 80.0 && p99 <= 100.0, "p99 = {p99}");
1013 }
1014
1015 #[test]
1016 fn test_countmin_basic() {
1017 let mut cms = CountMinSketch1024x5::new();
1018
1019 for _ in 0..100 {
1020 cms.insert_hash(1, 1);
1021 }
1022 for _ in 0..50 {
1023 cms.insert_hash(2, 1);
1024 }
1025 for _ in 0..10 {
1026 cms.insert_hash(3, 1);
1027 }
1028
1029 assert!(cms.estimate_hash(1) >= 100);
1030 assert!(cms.estimate_hash(2) >= 50);
1031 assert!(cms.estimate_hash(3) >= 10);
1032 }
1033
1034 #[test]
1035 fn test_countmin_merge() {
1036 let mut cms1 = CountMinSketch1024x5::new();
1037 let mut cms2 = CountMinSketch1024x5::new();
1038
1039 for _ in 0..50 {
1040 cms1.insert_hash(1, 1);
1041 }
1042 for _ in 0..50 {
1043 cms2.insert_hash(1, 1);
1044 }
1045
1046 cms1.merge(&cms2);
1047 assert!(cms1.estimate_hash(1) >= 100);
1048 }
1049
1050 #[cfg(feature = "std")]
1053 #[test]
1054 fn test_heavy_hitters() {
1055 let mut hh = HeavyHitters5::new();
1056
1057 for _ in 0..100 {
1058 hh.insert_hash(1);
1059 }
1060 for _ in 0..50 {
1061 hh.insert_hash(2);
1062 }
1063 for _ in 0..30 {
1064 hh.insert_hash(3);
1065 }
1066 for _ in 0..10 {
1067 hh.insert_hash(4);
1068 }
1069 for _ in 0..5 {
1070 hh.insert_hash(5);
1071 }
1072
1073 let top: Vec<_> = hh.top().collect();
1074 assert_eq!(top.len(), 5);
1075
1076 assert_eq!(top[0].hash, 1);
1077 assert_eq!(top[1].hash, 2);
1078 assert_eq!(top[2].hash, 3);
1079 }
1080
1081 #[test]
1082 fn test_fnv_hash_bytes() {
1083 let h1 = FnvHasher::hash_bytes(b"hello");
1084 let h2 = FnvHasher::hash_bytes(b"hello");
1085 let h3 = FnvHasher::hash_bytes(b"world");
1086 assert_eq!(h1, h2);
1087 assert_ne!(h1, h3);
1088 }
1089
1090 #[test]
1091 fn test_fnv_hash_u128() {
1092 let h1 = FnvHasher::hash_u128(12345u128);
1093 let h2 = FnvHasher::hash_u128(12345u128);
1094 let h3 = FnvHasher::hash_u128(12346u128);
1095 assert_eq!(h1, h2);
1096 assert_ne!(h1, h3);
1097 }
1098
1099 #[test]
1100 fn test_fnv_hasher_default() {
1101 let h = FnvHasher::default();
1102 assert_eq!(h.state, FnvHasher::FNV_OFFSET);
1103 }
1104
1105 #[test]
1106 fn test_fnv_hasher_finish() {
1107 let mut h = FnvHasher::new();
1108 h.write(b"test");
1109 let result = h.finish();
1110 assert_ne!(result, 0);
1111 }
1112
1113 #[test]
1114 fn test_hyperloglog_empty() {
1115 let hll = HyperLogLog16::new();
1116 let estimate = hll.cardinality();
1117 assert_eq!(estimate, 0.0);
1118 }
1119
1120 #[test]
1121 fn test_hyperloglog_single() {
1122 let mut hll = HyperLogLog16::new();
1123 hll.insert_hash(FnvHasher::hash_u64(42));
1124 let estimate = hll.cardinality();
1125 assert!(estimate > 0.0 && estimate < 5.0, "estimate = {estimate}");
1126 }
1127
1128 #[test]
1129 fn test_hyperloglog_clear() {
1130 let mut hll = HyperLogLog16::new();
1131 for i in 0..100u64 {
1132 hll.insert_hash(FnvHasher::hash_u64(i));
1133 }
1134 assert!(hll.cardinality() > 0.0);
1135 hll.clear();
1136 assert_eq!(hll.cardinality(), 0.0);
1137 }
1138
1139 #[test]
1140 fn test_hyperloglog_registers() {
1141 let hll = HyperLogLog16::new();
1142 assert_eq!(hll.registers().len(), HyperLogLog16::M);
1143 }
1144
1145 #[test]
1146 fn test_hyperloglog_default() {
1147 let hll = HyperLogLog16::default();
1148 assert_eq!(hll.cardinality(), 0.0);
1149 }
1150
1151 #[test]
1152 fn test_hyperloglog_insert_bytes() {
1153 let mut hll = HyperLogLog16::new();
1154 hll.insert_bytes(b"test_item");
1155 assert!(hll.cardinality() > 0.0);
1156 }
1157
1158 #[test]
1159 fn test_hyperloglog_insert_generic() {
1160 let mut hll = HyperLogLog16::new();
1161 hll.insert(&42u64);
1162 hll.insert(&"hello");
1163 assert!(hll.cardinality() > 0.0);
1164 }
1165
1166 #[test]
1167 fn test_hyperloglog10_accuracy() {
1168 let mut hll = HyperLogLog10::new();
1169 for i in 0..1000u64 {
1170 hll.insert_hash(FnvHasher::hash_u64(i));
1171 }
1172 let estimate = hll.cardinality();
1173 assert!(
1175 estimate > 500.0 && estimate < 1500.0,
1176 "estimate = {estimate}"
1177 );
1178 }
1179
1180 #[test]
1181 fn test_hyperloglog12_accuracy() {
1182 let mut hll = HyperLogLog12::new();
1183 for i in 0..1000u64 {
1184 hll.insert_hash(FnvHasher::hash_u64(i));
1185 }
1186 let estimate = hll.cardinality();
1187 assert!(
1188 estimate > 700.0 && estimate < 1300.0,
1189 "estimate = {estimate}"
1190 );
1191 }
1192
1193 #[test]
1194 fn test_ddsketch_empty() {
1195 let sketch = DDSketch2048::new(0.01);
1196 assert_eq!(sketch.count(), 0);
1197 assert_eq!(sketch.quantile(0.5), 0.0);
1198 assert_eq!(sketch.mean(), 0.0);
1199 }
1200
1201 #[test]
1202 fn test_ddsketch_single() {
1203 let mut sketch = DDSketch2048::new(0.01);
1204 sketch.insert(42.0);
1205 assert_eq!(sketch.count(), 1);
1206 assert_eq!(sketch.sum(), 42.0);
1207 assert_eq!(sketch.min(), 42.0);
1208 assert_eq!(sketch.max(), 42.0);
1209 }
1210
1211 #[test]
1212 fn test_ddsketch_clear() {
1213 let mut sketch = DDSketch2048::new(0.01);
1214 for v in [10.0, 20.0, 30.0] {
1215 sketch.insert(v);
1216 }
1217 assert_eq!(sketch.count(), 3);
1218 sketch.clear();
1219 assert_eq!(sketch.count(), 0);
1220 assert_eq!(sketch.sum(), 0.0);
1221 }
1222
1223 #[test]
1224 fn test_ddsketch_alpha() {
1225 let sketch = DDSketch2048::new(0.05);
1226 assert!((sketch.alpha() - 0.05).abs() < f64::EPSILON);
1227 }
1228
1229 #[test]
1230 fn test_ddsketch_merge() {
1231 let mut s1 = DDSketch2048::new(0.01);
1232 let mut s2 = DDSketch2048::new(0.01);
1233 for v in [10.0, 20.0, 30.0] {
1234 s1.insert(v);
1235 }
1236 for v in [40.0, 50.0] {
1237 s2.insert(v);
1238 }
1239 s1.merge(&s2);
1240 assert_eq!(s1.count(), 5);
1241 assert!((s1.sum() - 150.0).abs() < 0.001);
1242 assert_eq!(s1.min(), 10.0);
1243 assert_eq!(s1.max(), 50.0);
1244 }
1245
1246 #[test]
1247 fn test_ddsketch128() {
1248 let mut sketch = DDSketch128::new(0.1);
1249 sketch.insert(100.0);
1250 assert_eq!(sketch.count(), 1);
1251 }
1252
1253 #[test]
1254 fn test_ddsketch_negative() {
1255 let mut sketch = DDSketch2048::new(0.01);
1256 sketch.insert(-5.0);
1257 sketch.insert(-10.0);
1258 sketch.insert(0.0);
1259 assert_eq!(sketch.count(), 3);
1260 }
1261
1262 #[test]
1263 fn test_countmin_empty() {
1264 let cms = CountMinSketch1024x5::new();
1265 assert_eq!(cms.estimate_hash(42), 0);
1266 assert_eq!(cms.total(), 0);
1267 }
1268
1269 #[test]
1270 fn test_countmin_total() {
1271 let mut cms = CountMinSketch1024x5::new();
1272 cms.insert_hash(1, 10);
1273 cms.insert_hash(2, 20);
1274 assert_eq!(cms.total(), 30);
1275 }
1276
1277 #[test]
1278 fn test_countmin_clear() {
1279 let mut cms = CountMinSketch1024x5::new();
1280 cms.insert_hash(1, 100);
1281 assert!(cms.estimate_hash(1) >= 100);
1282 cms.clear();
1283 assert_eq!(cms.estimate_hash(1), 0);
1284 assert_eq!(cms.total(), 0);
1285 }
1286
1287 #[test]
1288 fn test_countmin_default() {
1289 let cms = CountMinSketch1024x5::default();
1290 assert_eq!(cms.total(), 0);
1291 }
1292
1293 #[test]
1294 fn test_countmin_insert_generic() {
1295 let mut cms = CountMinSketch1024x5::new();
1296 cms.insert(&"hello");
1297 assert!(cms.estimate(&"hello") >= 1);
1298 }
1299
1300 #[test]
1301 fn test_countmin_insert_bytes() {
1302 let mut cms = CountMinSketch1024x5::new();
1303 cms.insert_bytes(b"test_key");
1304 assert!(cms.estimate_bytes(b"test_key") >= 1);
1305 }
1306
1307 #[test]
1308 fn test_countmin_error_bound() {
1309 let cms = CountMinSketch1024x5::new();
1310 let error = cms.error_bound();
1311 assert!(error > 0.0);
1312 }
1313
1314 #[test]
1315 fn test_countmin_confidence() {
1316 let cms = CountMinSketch1024x5::new();
1317 let conf = cms.confidence();
1318 assert!(conf > 0.0 && conf < 1.0);
1319 }
1320
1321 #[test]
1322 fn test_countmin_2048x7() {
1323 let mut cms = CountMinSketch2048x7::new();
1324 cms.insert_hash(42, 5);
1325 assert!(cms.estimate_hash(42) >= 5);
1326 }
1327
1328 #[test]
1329 fn test_countmin_4096x5() {
1330 let mut cms = CountMinSketch4096x5::new();
1331 cms.insert_hash(42, 5);
1332 assert!(cms.estimate_hash(42) >= 5);
1333 }
1334
1335 #[test]
1336 fn test_heavy_hitters_default() {
1337 let hh = HeavyHitters5::default();
1338 assert_eq!(hh.top().count(), 0);
1339 }
1340
1341 #[test]
1342 fn test_heavy_hitters_clear() {
1343 let mut hh = HeavyHitters5::new();
1344 for _ in 0..10 {
1345 hh.insert_hash(1);
1346 }
1347 assert!(hh.top().count() > 0);
1348 hh.clear();
1349 assert_eq!(hh.top().count(), 0);
1350 }
1351
1352 #[test]
1353 fn test_heavy_hitters_cms_access() {
1354 let mut hh = HeavyHitters5::new();
1355 hh.insert_hash(42);
1356 assert!(hh.cms().estimate_hash(42) >= 1);
1357 }
1358
1359 #[test]
1360 fn test_heavy_hitters_10() {
1361 let mut hh = HeavyHitters10::new();
1362 for i in 0..10u64 {
1363 for _ in 0..(10 - i) {
1364 hh.insert_hash(i);
1365 }
1366 }
1367 assert_eq!(hh.top().count(), 10);
1368 }
1369
1370 #[test]
1371 fn test_heavy_hitters_20() {
1372 let mut hh = HeavyHitters20::new();
1373 hh.insert_hash(1);
1374 assert_eq!(hh.top().count(), 1);
1375 }
1376
1377 #[test]
1378 fn test_fast_log2_approx() {
1379 let approx = fast_log2_approx(1024.0);
1380 assert!((approx - 10.0).abs() < 0.1, "approx = {approx}");
1381 }
1382
1383 #[test]
1384 fn test_fast_log2_approx_one() {
1385 let approx = fast_log2_approx(1.0);
1386 assert!(approx.abs() < 0.01, "approx = {approx}");
1387 }
1388
1389 #[test]
1390 fn test_hyperloglog14_type_alias() {
1391 let mut hll = HyperLogLog::new();
1392 hll.insert_hash(FnvHasher::hash_u64(1));
1393 assert!(hll.cardinality() > 0.0);
1394 }
1395
1396 #[test]
1397 fn test_ddsketch_type_alias() {
1398 let mut sketch = DDSketch::new(0.01);
1399 sketch.insert(42.0);
1400 assert_eq!(sketch.count(), 1);
1401 }
1402
1403 #[test]
1404 fn test_countmin_type_alias() {
1405 let mut cms = CountMinSketch::new();
1406 cms.insert_hash(1, 1);
1407 assert!(cms.estimate_hash(1) >= 1);
1408 }
1409
1410 #[test]
1411 fn test_heavy_hitters_type_alias() {
1412 let mut hh = HeavyHitters::new();
1413 hh.insert_hash(1);
1414 assert_eq!(hh.top().count(), 1);
1415 }
1416}