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 self.min = alice_det_math::min64(self.min, value);
448 self.max = alice_det_math::max64(self.max, value);
449
450 if value > 0.0 {
455 let idx = self.bucket_index(value).min($bins - 1);
456 self.positive_bins[idx] += 1;
457 } else if value < 0.0 {
458 let idx = self.bucket_index(-value).min($bins - 1);
459 self.negative_bins[idx] += 1;
460 } else {
461 self.zero_count += 1;
462 }
463 }
464
465 #[inline]
483 fn bucket_index(&self, value: f64) -> usize {
484 let idx =
485 f64_i32((ln64(value) * self.inv_ln_gamma).ceil()).saturating_add(self.offset);
486 usize::try_from(idx.max(0)).unwrap_or(0)
487 }
488
489 #[inline(always)]
492 #[allow(dead_code)]
493 fn bucket_index_fast(&self, value: f64) -> usize {
494 let log2_gamma = self.ln_gamma / core::f64::consts::LN_2;
495 let inv_log2_gamma = 1.0 / log2_gamma;
496 let log2_value = fast_log2_approx(value);
497 let idx = f64_i32((log2_value * inv_log2_gamma).ceil()).saturating_add(self.offset);
498 usize::try_from(idx.max(0)).unwrap_or(0)
499 }
500
501 #[inline]
511 fn bucket_representative(&self, idx: usize) -> f64 {
512 let exp = i32::try_from(idx).unwrap_or(i32::MAX) - self.offset;
513 2.0 * ipow64(self.gamma, exp) / (self.gamma + 1.0)
514 }
515
516 pub fn quantile(&self, q: f64) -> f64 {
517 if self.count == 0 {
518 return 0.0;
519 }
520 let rank = f64_u64((q * u64_f64(self.count)).ceil()).max(1);
521 let clamp = |v: f64| v.clamp(self.min, self.max);
524 let mut cumulative = 0u64;
525 for (idx, &count) in self.negative_bins.iter().enumerate().rev() {
530 cumulative += count;
531 if cumulative >= rank {
532 return clamp(-self.bucket_representative(idx));
533 }
534 }
535 cumulative += self.zero_count;
536 if cumulative >= rank {
537 return 0.0;
538 }
539 for (idx, &count) in self.positive_bins.iter().enumerate() {
540 cumulative += count;
541 if cumulative >= rank {
542 return clamp(self.bucket_representative(idx));
543 }
544 }
545 self.max
546 }
547 #[inline]
552 pub const fn count(&self) -> u64 {
553 self.count
554 }
555
556 #[inline]
564 pub const fn non_finite(&self) -> u64 {
565 self.non_finite
566 }
567
568 #[inline]
572 pub const fn sum(&self) -> f64 {
573 self.sum
574 }
575
576 #[inline(always)]
579 pub fn mean(&self) -> f64 {
580 if self.count == 0 {
581 0.0
582 } else {
583 self.sum * (1.0 / u64_f64(self.count))
584 }
585 }
586
587 #[inline]
590 pub const fn min(&self) -> f64 {
591 self.min
592 }
593
594 #[inline]
597 pub const fn max(&self) -> f64 {
598 self.max
599 }
600
601 #[inline]
602 pub const fn alpha(&self) -> f64 {
603 self.alpha
604 }
605
606 #[must_use]
614 pub fn accurate_range(&self) -> (f64, f64) {
615 let lowest = -self.offset;
616 let highest = i32::try_from($bins - 1).unwrap_or(i32::MAX) - self.offset;
617 (ipow64(self.gamma, lowest), ipow64(self.gamma, highest))
618 }
619
620 pub const fn clear(&mut self) {
621 self.positive_bins = [0u64; $bins];
622 self.negative_bins = [0u64; $bins];
623 self.zero_count = 0;
624 self.count = 0;
625 self.non_finite = 0;
626 self.min = f64::INFINITY;
627 self.max = f64::NEG_INFINITY;
628 self.sum = 0.0;
629 }
630 }
631
632 impl Mergeable for $name {
633 fn merge(&mut self, other: &Self) {
634 for (dst, &src) in self
635 .positive_bins
636 .iter_mut()
637 .zip(other.positive_bins.iter())
638 {
639 *dst += src;
640 }
641 for (dst, &src) in self
642 .negative_bins
643 .iter_mut()
644 .zip(other.negative_bins.iter())
645 {
646 *dst += src;
647 }
648 self.zero_count += other.zero_count;
649 self.count += other.count;
650 self.non_finite += other.non_finite;
651 self.sum += other.sum;
652
653 self.min = alice_det_math::min64(self.min, other.min);
654 self.max = alice_det_math::max64(self.max, other.max);
655 }
656 }
657 };
658}
659
660impl_ddsketch!(DDSketch128, 128); impl_ddsketch!(DDSketch256, 256); impl_ddsketch!(DDSketch512, 512); impl_ddsketch!(DDSketch1024, 1024); impl_ddsketch!(DDSketch2048, 2048); pub type DDSketch = DDSketch2048;
669
670macro_rules! impl_countmin {
676 ($name:ident, $w:expr, $d:expr) => {
677 #[derive(Clone, Debug)]
679 pub struct $name {
680 counters: [[u64; $w]; $d],
681 total: u64,
682 }
683
684 impl $name {
685 pub const WIDTH: usize = $w;
686 pub const DEPTH: usize = $d;
687
688 #[inline]
689 pub const fn new() -> Self {
690 #[allow(clippy::large_stack_arrays)]
691 Self {
692 counters: [[0u64; $w]; $d],
693 total: 0,
694 }
695 }
696
697 #[inline]
698 const fn hash_for_row(hash: u64, row: usize) -> usize {
699 let h = hash.wrapping_add((row as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15));
700 let mixed = h ^ (h >> 33);
701 let mixed = mixed.wrapping_mul(0xff51_afd7_ed55_8ccd);
702 let mixed = mixed ^ (mixed >> 33);
703 hash_index(mixed) & ($w - 1)
704 }
705
706 #[inline]
707 pub fn insert_hash(&mut self, hash: u64, count: u64) {
708 self.total += count;
709 for row in 0..$d {
710 let col = Self::hash_for_row(hash, row);
711 self.counters[row][col] = self.counters[row][col].saturating_add(count);
712 }
713 }
714
715 #[inline]
716 pub fn insert<T: Hash>(&mut self, item: &T) {
717 let mut hasher = FnvHasher::new();
718 item.hash(&mut hasher);
719 self.insert_hash(hasher.finish(), 1);
720 }
721
722 #[inline]
723 pub fn insert_bytes(&mut self, bytes: &[u8]) {
724 self.insert_hash(FnvHasher::hash_bytes(bytes), 1);
725 }
726
727 #[inline]
728 pub fn estimate_hash(&self, hash: u64) -> u64 {
729 let mut min_count = u64::MAX;
730 for row in 0..$d {
731 let col = Self::hash_for_row(hash, row);
732 min_count = min_count.min(self.counters[row][col]);
733 }
734 min_count
735 }
736
737 #[inline]
738 pub fn estimate<T: Hash>(&self, item: &T) -> u64 {
739 let mut hasher = FnvHasher::new();
740 item.hash(&mut hasher);
741 self.estimate_hash(hasher.finish())
742 }
743
744 #[inline]
745 pub fn estimate_bytes(&self, bytes: &[u8]) -> u64 {
746 self.estimate_hash(FnvHasher::hash_bytes(bytes))
747 }
748
749 #[inline]
750 pub const fn total(&self) -> u64 {
751 self.total
752 }
753
754 #[inline]
755 pub const fn clear(&mut self) {
756 #[allow(clippy::large_stack_arrays)]
757 {
758 self.counters = [[0u64; $w]; $d];
759 }
760 self.total = 0;
761 }
762
763 #[inline(always)]
764 pub fn error_bound(&self) -> f64 {
765 core::f64::consts::E * (1.0 / f64::from($w))
766 }
767
768 #[inline]
769 pub fn confidence(&self) -> f64 {
770 1.0 - exp64(-f64::from($d))
771 }
772 }
773
774 impl Default for $name {
775 fn default() -> Self {
776 Self::new()
777 }
778 }
779
780 impl Mergeable for $name {
781 fn merge(&mut self, other: &Self) {
782 self.total += other.total;
783 for row in 0..$d {
784 for col in 0..$w {
785 self.counters[row][col] =
786 self.counters[row][col].saturating_add(other.counters[row][col]);
787 }
788 }
789 }
790 }
791 };
792}
793
794impl_countmin!(CountMinSketch1024x5, 1024, 5);
796impl_countmin!(CountMinSketch2048x7, 2048, 7);
797impl_countmin!(CountMinSketch4096x5, 4096, 5);
798
799pub type CountMinSketch = CountMinSketch1024x5;
801
802#[derive(Clone, Copy, Debug)]
808pub struct HeavyHitterEntry {
809 pub hash: u64,
811 pub count: u64,
813}
814
815macro_rules! impl_heavy_hitters {
817 ($name:ident, $cms_name:ident, $k:expr) => {
818 #[derive(Clone, Debug)]
820 pub struct $name {
821 cms: $cms_name,
822 top_k: [HeavyHitterEntry; $k],
823 count: usize,
824 }
825
826 impl $name {
827 pub const K: usize = $k;
828
829 #[inline]
830 pub const fn new() -> Self {
831 Self {
832 cms: $cms_name::new(),
833 top_k: [HeavyHitterEntry { hash: 0, count: 0 }; $k],
834 count: 0,
835 }
836 }
837
838 pub fn insert_hash(&mut self, hash: u64) {
839 self.cms.insert_hash(hash, 1);
840 let estimated = self.cms.estimate_hash(hash);
841
842 let mut found_idx = None;
843 for i in 0..self.count {
844 if self.top_k[i].hash == hash {
845 found_idx = Some(i);
846 break;
847 }
848 }
849
850 if let Some(idx) = found_idx {
851 self.top_k[idx].count = estimated;
852 self.sort_top_k();
853 } else if self.count < $k {
854 self.top_k[self.count] = HeavyHitterEntry {
855 hash,
856 count: estimated,
857 };
858 self.count += 1;
859 self.sort_top_k();
860 } else if estimated > self.top_k[0].count {
861 self.top_k[0] = HeavyHitterEntry {
862 hash,
863 count: estimated,
864 };
865 self.sort_top_k();
866 }
867 }
868
869 fn sort_top_k(&mut self) {
870 for i in 1..self.count {
871 let entry = self.top_k[i];
872 let mut j = i;
873 while j > 0 && self.top_k[j - 1].count > entry.count {
874 self.top_k[j] = self.top_k[j - 1];
875 j -= 1;
876 }
877 self.top_k[j] = entry;
878 }
879 }
880
881 pub fn top(&self) -> impl Iterator<Item = &HeavyHitterEntry> {
882 self.top_k[..self.count].iter().rev()
883 }
884
885 #[inline]
886 pub const fn cms(&self) -> &$cms_name {
887 &self.cms
888 }
889
890 pub const fn clear(&mut self) {
891 self.cms.clear();
892 self.top_k = [HeavyHitterEntry { hash: 0, count: 0 }; $k];
893 self.count = 0;
894 }
895 }
896
897 impl Default for $name {
898 fn default() -> Self {
899 Self::new()
900 }
901 }
902 };
903}
904
905impl_heavy_hitters!(HeavyHitters10, CountMinSketch1024x5, 10);
907impl_heavy_hitters!(HeavyHitters20, CountMinSketch2048x7, 20);
908impl_heavy_hitters!(HeavyHitters5, CountMinSketch1024x5, 5);
909
910pub type HeavyHitters = HeavyHitters10;
912
913#[cfg(test)]
918#[allow(clippy::float_cmp)]
919mod tests {
920 use super::*;
921
922 #[test]
923 fn test_fnv_hash() {
924 let h1 = FnvHasher::hash_u64(12345);
925 let h2 = FnvHasher::hash_u64(12345);
926 let h3 = FnvHasher::hash_u64(12346);
927
928 assert_eq!(h1, h2);
929 assert_ne!(h1, h3);
930 }
931
932 #[test]
933 fn test_hyperloglog_basic() {
934 let mut hll = HyperLogLog16::new();
936
937 for i in 0..1000u64 {
939 hll.insert_hash(FnvHasher::hash_u64(i));
940 }
941
942 let estimate = hll.cardinality();
943 assert!(
945 estimate > 800.0 && estimate < 1200.0,
946 "estimate = {estimate}"
947 );
948 }
949
950 #[test]
951 fn test_hyperloglog_merge() {
952 let mut hll1 = HyperLogLog16::new();
953 let mut hll2 = HyperLogLog16::new();
954
955 for i in 0..500u64 {
956 hll1.insert_hash(FnvHasher::hash_u64(i));
957 }
958 for i in 500..1000u64 {
959 hll2.insert_hash(FnvHasher::hash_u64(i));
960 }
961
962 hll1.merge(&hll2);
963 let estimate = hll1.cardinality();
964 assert!(
965 estimate > 800.0 && estimate < 1200.0,
966 "estimate = {estimate}"
967 );
968 }
969
970 #[test]
971 fn test_hyperloglog_16_large() {
972 let mut hll = HyperLogLog16::new();
973
974 for i in 0..100_000_u64 {
976 hll.insert_hash(FnvHasher::hash_u64(i));
977 }
978
979 let estimate = hll.cardinality();
980 assert!(
982 estimate > 75000.0 && estimate < 125_000.0,
983 "estimate = {estimate}"
984 );
985 }
986
987 #[test]
988 fn test_ddsketch_basic() {
989 let mut sketch = DDSketch2048::new(0.01);
991
992 let values = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0];
993 for v in values {
994 sketch.insert(v);
995 }
996
997 assert_eq!(sketch.count(), 10);
998 assert!((sketch.mean() - 55.0).abs() < 0.001);
999
1000 let p50 = sketch.quantile(0.5);
1001 assert!(p50 > 40.0 && p50 < 70.0, "p50 = {p50}");
1002
1003 let p99 = sketch.quantile(0.99);
1004 assert!(p99 > 80.0 && p99 <= 100.0, "p99 = {p99}");
1005 }
1006
1007 #[test]
1008 fn test_countmin_basic() {
1009 let mut cms = CountMinSketch1024x5::new();
1010
1011 for _ in 0..100 {
1012 cms.insert_hash(1, 1);
1013 }
1014 for _ in 0..50 {
1015 cms.insert_hash(2, 1);
1016 }
1017 for _ in 0..10 {
1018 cms.insert_hash(3, 1);
1019 }
1020
1021 assert!(cms.estimate_hash(1) >= 100);
1022 assert!(cms.estimate_hash(2) >= 50);
1023 assert!(cms.estimate_hash(3) >= 10);
1024 }
1025
1026 #[test]
1027 fn test_countmin_merge() {
1028 let mut cms1 = CountMinSketch1024x5::new();
1029 let mut cms2 = CountMinSketch1024x5::new();
1030
1031 for _ in 0..50 {
1032 cms1.insert_hash(1, 1);
1033 }
1034 for _ in 0..50 {
1035 cms2.insert_hash(1, 1);
1036 }
1037
1038 cms1.merge(&cms2);
1039 assert!(cms1.estimate_hash(1) >= 100);
1040 }
1041
1042 #[cfg(feature = "std")]
1045 #[test]
1046 fn test_heavy_hitters() {
1047 let mut hh = HeavyHitters5::new();
1048
1049 for _ in 0..100 {
1050 hh.insert_hash(1);
1051 }
1052 for _ in 0..50 {
1053 hh.insert_hash(2);
1054 }
1055 for _ in 0..30 {
1056 hh.insert_hash(3);
1057 }
1058 for _ in 0..10 {
1059 hh.insert_hash(4);
1060 }
1061 for _ in 0..5 {
1062 hh.insert_hash(5);
1063 }
1064
1065 let top: Vec<_> = hh.top().collect();
1066 assert_eq!(top.len(), 5);
1067
1068 assert_eq!(top[0].hash, 1);
1069 assert_eq!(top[1].hash, 2);
1070 assert_eq!(top[2].hash, 3);
1071 }
1072
1073 #[test]
1074 fn test_fnv_hash_bytes() {
1075 let h1 = FnvHasher::hash_bytes(b"hello");
1076 let h2 = FnvHasher::hash_bytes(b"hello");
1077 let h3 = FnvHasher::hash_bytes(b"world");
1078 assert_eq!(h1, h2);
1079 assert_ne!(h1, h3);
1080 }
1081
1082 #[test]
1083 fn test_fnv_hash_u128() {
1084 let h1 = FnvHasher::hash_u128(12345u128);
1085 let h2 = FnvHasher::hash_u128(12345u128);
1086 let h3 = FnvHasher::hash_u128(12346u128);
1087 assert_eq!(h1, h2);
1088 assert_ne!(h1, h3);
1089 }
1090
1091 #[test]
1092 fn test_fnv_hasher_default() {
1093 let h = FnvHasher::default();
1094 assert_eq!(h.state, FnvHasher::FNV_OFFSET);
1095 }
1096
1097 #[test]
1098 fn test_fnv_hasher_finish() {
1099 let mut h = FnvHasher::new();
1100 h.write(b"test");
1101 let result = h.finish();
1102 assert_ne!(result, 0);
1103 }
1104
1105 #[test]
1106 fn test_hyperloglog_empty() {
1107 let hll = HyperLogLog16::new();
1108 let estimate = hll.cardinality();
1109 assert_eq!(estimate, 0.0);
1110 }
1111
1112 #[test]
1113 fn test_hyperloglog_single() {
1114 let mut hll = HyperLogLog16::new();
1115 hll.insert_hash(FnvHasher::hash_u64(42));
1116 let estimate = hll.cardinality();
1117 assert!(estimate > 0.0 && estimate < 5.0, "estimate = {estimate}");
1118 }
1119
1120 #[test]
1121 fn test_hyperloglog_clear() {
1122 let mut hll = HyperLogLog16::new();
1123 for i in 0..100u64 {
1124 hll.insert_hash(FnvHasher::hash_u64(i));
1125 }
1126 assert!(hll.cardinality() > 0.0);
1127 hll.clear();
1128 assert_eq!(hll.cardinality(), 0.0);
1129 }
1130
1131 #[test]
1132 fn test_hyperloglog_registers() {
1133 let hll = HyperLogLog16::new();
1134 assert_eq!(hll.registers().len(), HyperLogLog16::M);
1135 }
1136
1137 #[test]
1138 fn test_hyperloglog_default() {
1139 let hll = HyperLogLog16::default();
1140 assert_eq!(hll.cardinality(), 0.0);
1141 }
1142
1143 #[test]
1144 fn test_hyperloglog_insert_bytes() {
1145 let mut hll = HyperLogLog16::new();
1146 hll.insert_bytes(b"test_item");
1147 assert!(hll.cardinality() > 0.0);
1148 }
1149
1150 #[test]
1151 fn test_hyperloglog_insert_generic() {
1152 let mut hll = HyperLogLog16::new();
1153 hll.insert(&42u64);
1154 hll.insert(&"hello");
1155 assert!(hll.cardinality() > 0.0);
1156 }
1157
1158 #[test]
1159 fn test_hyperloglog10_accuracy() {
1160 let mut hll = HyperLogLog10::new();
1161 for i in 0..1000u64 {
1162 hll.insert_hash(FnvHasher::hash_u64(i));
1163 }
1164 let estimate = hll.cardinality();
1165 assert!(
1167 estimate > 500.0 && estimate < 1500.0,
1168 "estimate = {estimate}"
1169 );
1170 }
1171
1172 #[test]
1173 fn test_hyperloglog12_accuracy() {
1174 let mut hll = HyperLogLog12::new();
1175 for i in 0..1000u64 {
1176 hll.insert_hash(FnvHasher::hash_u64(i));
1177 }
1178 let estimate = hll.cardinality();
1179 assert!(
1180 estimate > 700.0 && estimate < 1300.0,
1181 "estimate = {estimate}"
1182 );
1183 }
1184
1185 #[test]
1186 fn test_ddsketch_empty() {
1187 let sketch = DDSketch2048::new(0.01);
1188 assert_eq!(sketch.count(), 0);
1189 assert_eq!(sketch.quantile(0.5), 0.0);
1190 assert_eq!(sketch.mean(), 0.0);
1191 }
1192
1193 #[test]
1194 fn test_ddsketch_single() {
1195 let mut sketch = DDSketch2048::new(0.01);
1196 sketch.insert(42.0);
1197 assert_eq!(sketch.count(), 1);
1198 assert_eq!(sketch.sum(), 42.0);
1199 assert_eq!(sketch.min(), 42.0);
1200 assert_eq!(sketch.max(), 42.0);
1201 }
1202
1203 #[test]
1204 fn test_ddsketch_clear() {
1205 let mut sketch = DDSketch2048::new(0.01);
1206 for v in [10.0, 20.0, 30.0] {
1207 sketch.insert(v);
1208 }
1209 assert_eq!(sketch.count(), 3);
1210 sketch.clear();
1211 assert_eq!(sketch.count(), 0);
1212 assert_eq!(sketch.sum(), 0.0);
1213 }
1214
1215 #[test]
1216 fn test_ddsketch_alpha() {
1217 let sketch = DDSketch2048::new(0.05);
1218 assert!((sketch.alpha() - 0.05).abs() < f64::EPSILON);
1219 }
1220
1221 #[test]
1222 fn test_ddsketch_merge() {
1223 let mut s1 = DDSketch2048::new(0.01);
1224 let mut s2 = DDSketch2048::new(0.01);
1225 for v in [10.0, 20.0, 30.0] {
1226 s1.insert(v);
1227 }
1228 for v in [40.0, 50.0] {
1229 s2.insert(v);
1230 }
1231 s1.merge(&s2);
1232 assert_eq!(s1.count(), 5);
1233 assert!((s1.sum() - 150.0).abs() < 0.001);
1234 assert_eq!(s1.min(), 10.0);
1235 assert_eq!(s1.max(), 50.0);
1236 }
1237
1238 #[test]
1239 fn test_ddsketch128() {
1240 let mut sketch = DDSketch128::new(0.1);
1241 sketch.insert(100.0);
1242 assert_eq!(sketch.count(), 1);
1243 }
1244
1245 #[test]
1246 fn test_ddsketch_negative() {
1247 let mut sketch = DDSketch2048::new(0.01);
1248 sketch.insert(-5.0);
1249 sketch.insert(-10.0);
1250 sketch.insert(0.0);
1251 assert_eq!(sketch.count(), 3);
1252 }
1253
1254 #[test]
1255 fn test_countmin_empty() {
1256 let cms = CountMinSketch1024x5::new();
1257 assert_eq!(cms.estimate_hash(42), 0);
1258 assert_eq!(cms.total(), 0);
1259 }
1260
1261 #[test]
1262 fn test_countmin_total() {
1263 let mut cms = CountMinSketch1024x5::new();
1264 cms.insert_hash(1, 10);
1265 cms.insert_hash(2, 20);
1266 assert_eq!(cms.total(), 30);
1267 }
1268
1269 #[test]
1270 fn test_countmin_clear() {
1271 let mut cms = CountMinSketch1024x5::new();
1272 cms.insert_hash(1, 100);
1273 assert!(cms.estimate_hash(1) >= 100);
1274 cms.clear();
1275 assert_eq!(cms.estimate_hash(1), 0);
1276 assert_eq!(cms.total(), 0);
1277 }
1278
1279 #[test]
1280 fn test_countmin_default() {
1281 let cms = CountMinSketch1024x5::default();
1282 assert_eq!(cms.total(), 0);
1283 }
1284
1285 #[test]
1286 fn test_countmin_insert_generic() {
1287 let mut cms = CountMinSketch1024x5::new();
1288 cms.insert(&"hello");
1289 assert!(cms.estimate(&"hello") >= 1);
1290 }
1291
1292 #[test]
1293 fn test_countmin_insert_bytes() {
1294 let mut cms = CountMinSketch1024x5::new();
1295 cms.insert_bytes(b"test_key");
1296 assert!(cms.estimate_bytes(b"test_key") >= 1);
1297 }
1298
1299 #[test]
1300 fn test_countmin_error_bound() {
1301 let cms = CountMinSketch1024x5::new();
1302 let error = cms.error_bound();
1303 assert!(error > 0.0);
1304 }
1305
1306 #[test]
1307 fn test_countmin_confidence() {
1308 let cms = CountMinSketch1024x5::new();
1309 let conf = cms.confidence();
1310 assert!(conf > 0.0 && conf < 1.0);
1311 }
1312
1313 #[test]
1314 fn test_countmin_2048x7() {
1315 let mut cms = CountMinSketch2048x7::new();
1316 cms.insert_hash(42, 5);
1317 assert!(cms.estimate_hash(42) >= 5);
1318 }
1319
1320 #[test]
1321 fn test_countmin_4096x5() {
1322 let mut cms = CountMinSketch4096x5::new();
1323 cms.insert_hash(42, 5);
1324 assert!(cms.estimate_hash(42) >= 5);
1325 }
1326
1327 #[test]
1328 fn test_heavy_hitters_default() {
1329 let hh = HeavyHitters5::default();
1330 assert_eq!(hh.top().count(), 0);
1331 }
1332
1333 #[test]
1334 fn test_heavy_hitters_clear() {
1335 let mut hh = HeavyHitters5::new();
1336 for _ in 0..10 {
1337 hh.insert_hash(1);
1338 }
1339 assert!(hh.top().count() > 0);
1340 hh.clear();
1341 assert_eq!(hh.top().count(), 0);
1342 }
1343
1344 #[test]
1345 fn test_heavy_hitters_cms_access() {
1346 let mut hh = HeavyHitters5::new();
1347 hh.insert_hash(42);
1348 assert!(hh.cms().estimate_hash(42) >= 1);
1349 }
1350
1351 #[test]
1352 fn test_heavy_hitters_10() {
1353 let mut hh = HeavyHitters10::new();
1354 for i in 0..10u64 {
1355 for _ in 0..(10 - i) {
1356 hh.insert_hash(i);
1357 }
1358 }
1359 assert_eq!(hh.top().count(), 10);
1360 }
1361
1362 #[test]
1363 fn test_heavy_hitters_20() {
1364 let mut hh = HeavyHitters20::new();
1365 hh.insert_hash(1);
1366 assert_eq!(hh.top().count(), 1);
1367 }
1368
1369 #[test]
1370 fn test_fast_log2_approx() {
1371 let approx = fast_log2_approx(1024.0);
1372 assert!((approx - 10.0).abs() < 0.1, "approx = {approx}");
1373 }
1374
1375 #[test]
1376 fn test_fast_log2_approx_one() {
1377 let approx = fast_log2_approx(1.0);
1378 assert!(approx.abs() < 0.01, "approx = {approx}");
1379 }
1380
1381 #[test]
1382 fn test_hyperloglog14_type_alias() {
1383 let mut hll = HyperLogLog::new();
1384 hll.insert_hash(FnvHasher::hash_u64(1));
1385 assert!(hll.cardinality() > 0.0);
1386 }
1387
1388 #[test]
1389 fn test_ddsketch_type_alias() {
1390 let mut sketch = DDSketch::new(0.01);
1391 sketch.insert(42.0);
1392 assert_eq!(sketch.count(), 1);
1393 }
1394
1395 #[test]
1396 fn test_countmin_type_alias() {
1397 let mut cms = CountMinSketch::new();
1398 cms.insert_hash(1, 1);
1399 assert!(cms.estimate_hash(1) >= 1);
1400 }
1401
1402 #[test]
1403 fn test_heavy_hitters_type_alias() {
1404 let mut hh = HeavyHitters::new();
1405 hh.insert_hash(1);
1406 assert_eq!(hh.top().count(), 1);
1407 }
1408}