1use ordered_float::OrderedFloat;
32use std::cmp::Ordering;
33
34#[cfg(feature = "use_serde")]
35use serde::{Deserialize, Serialize};
36
37#[cfg(feature = "use_rkyv")]
38use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
39
40#[derive(Debug, PartialEq, Eq, Clone)]
42#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
43#[cfg_attr(feature = "use_rkyv", derive(Archive, RkyvDeserialize, RkyvSerialize))]
44pub struct Centroid {
45 mean: OrderedFloat<f64>,
46 weight: OrderedFloat<f64>,
47}
48
49impl PartialOrd for Centroid {
50 fn partial_cmp(&self, other: &Centroid) -> Option<Ordering> {
51 Some(self.cmp(other))
52 }
53}
54
55impl Ord for Centroid {
56 fn cmp(&self, other: &Centroid) -> Ordering {
57 self.mean.cmp(&other.mean)
58 }
59}
60
61impl Centroid {
62 pub fn new(mean: f64, weight: f64) -> Self {
63 Centroid {
64 mean: OrderedFloat::from(mean),
65 weight: OrderedFloat::from(weight),
66 }
67 }
68
69 #[inline]
70 pub fn mean(&self) -> f64 {
71 self.mean.into_inner()
72 }
73
74 #[inline]
75 pub fn weight(&self) -> f64 {
76 self.weight.into_inner()
77 }
78
79 pub fn add(&mut self, sum: f64, weight: f64) -> f64 {
80 let weight_: f64 = self.weight.into_inner();
81 let mean_: f64 = self.mean.into_inner();
82
83 let new_sum: f64 = sum + weight_ * mean_;
84 let new_weight: f64 = weight_ + weight;
85 self.weight = OrderedFloat::from(new_weight);
86 self.mean = OrderedFloat::from(new_sum / new_weight);
87 new_sum
88 }
89}
90
91impl Default for Centroid {
92 fn default() -> Self {
93 Centroid {
94 mean: OrderedFloat::from(0.0),
95 weight: OrderedFloat::from(1.0),
96 }
97 }
98}
99
100#[derive(Debug, PartialEq, Eq, Clone)]
102#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
103#[cfg_attr(feature = "use_rkyv", derive(Archive, RkyvDeserialize, RkyvSerialize))]
104pub struct TDigest {
105 centroids: Vec<Centroid>,
106 max_size: usize,
107 sum: OrderedFloat<f64>,
108 count: OrderedFloat<f64>,
109 max: OrderedFloat<f64>,
110 min: OrderedFloat<f64>,
111}
112
113impl TDigest {
114 pub fn new_with_size(max_size: usize) -> Self {
115 TDigest {
116 centroids: Vec::new(),
117 max_size,
118 sum: OrderedFloat::from(0.0),
119 count: OrderedFloat::from(0.0),
120 max: OrderedFloat::from(std::f64::NAN),
121 min: OrderedFloat::from(std::f64::NAN),
122 }
123 }
124
125 pub fn new(centroids: Vec<Centroid>, sum: f64, count: f64, max: f64, min: f64, max_size: usize) -> Self {
126 if centroids.len() <= max_size {
127 TDigest {
128 centroids,
129 max_size,
130 sum: OrderedFloat::from(sum),
131 count: OrderedFloat::from(count),
132 max: OrderedFloat::from(max),
133 min: OrderedFloat::from(min),
134 }
135 } else {
136 let sz = centroids.len();
137 let digests: Vec<TDigest> = vec![
138 TDigest::new_with_size(100),
139 TDigest::new(centroids, sum, count, max, min, sz),
140 ];
141
142 Self::merge_digests(digests)
143 }
144 }
145
146 #[inline]
147 pub fn mean(&self) -> f64 {
148 let count_: f64 = self.count.into_inner();
149 let sum_: f64 = self.sum.into_inner();
150
151 if count_ > 0.0 {
152 sum_ / count_
153 } else {
154 0.0
155 }
156 }
157
158 #[inline]
159 pub fn sum(&self) -> f64 {
160 self.sum.into_inner()
161 }
162
163 #[inline]
164 pub fn count(&self) -> f64 {
165 self.count.into_inner()
166 }
167
168 #[inline]
169 pub fn max(&self) -> f64 {
170 self.max.into_inner()
171 }
172
173 #[inline]
174 pub fn min(&self) -> f64 {
175 self.min.into_inner()
176 }
177
178 #[inline]
179 pub fn is_empty(&self) -> bool {
180 self.centroids.is_empty()
181 }
182
183 #[inline]
184 pub fn max_size(&self) -> usize {
185 self.max_size
186 }
187
188 pub fn add(&mut self, value: f64) {
190 let prev_count = self.count.into_inner();
192 self.count = OrderedFloat::from(prev_count + 1.0);
193 self.sum = OrderedFloat::from(self.sum.into_inner() + value);
194
195 if prev_count == 0.0 {
197 self.min = OrderedFloat::from(value);
198 self.max = OrderedFloat::from(value);
199 } else {
200 if value < self.min.into_inner() {
201 self.min = OrderedFloat::from(value);
202 }
203 if value > self.max.into_inner() {
204 self.max = OrderedFloat::from(value);
205 }
206 }
207
208 if self.centroids.is_empty() {
210 self.centroids.push(Centroid::new(value, 1.0));
211 return;
212 }
213
214 let pos = self.centroids
216 .binary_search_by(|c| c.mean.partial_cmp(&OrderedFloat::from(value)).unwrap())
217 .unwrap_or_else(|i| i);
218
219 let mut merged = false;
221 let total_count = self.count.into_inner();
222
223 if pos > 0 {
225 let idx = pos - 1;
226 if self.can_merge_at(idx, 1.0, total_count) {
227 let weight = self.centroids[idx].weight();
228 let mean = self.centroids[idx].mean();
229 let new_mean = (mean * weight + value) / (weight + 1.0);
230 self.centroids[idx] = Centroid::new(new_mean, weight + 1.0);
231 merged = true;
232 }
233 }
234
235 if !merged && pos < self.centroids.len() {
237 if self.can_merge_at(pos, 1.0, total_count) {
238 let weight = self.centroids[pos].weight();
239 let mean = self.centroids[pos].mean();
240 let new_mean = (mean * weight + value) / (weight + 1.0);
241 self.centroids[pos] = Centroid::new(new_mean, weight + 1.0);
242 merged = true;
243 }
244 }
245
246 if !merged {
248 self.centroids.insert(pos, Centroid::new(value, 1.0));
249
250 if self.centroids.len() > self.max_size {
252 self.compress();
253 }
254 }
255 }
256
257 fn can_merge_at(&self, idx: usize, new_weight: f64, total_count: f64) -> bool {
259 let mut k = 0.0;
261 for i in 0..idx {
262 k += self.centroids[i].weight();
263 }
264 k += self.centroids[idx].weight() / 2.0;
265
266 let q_current = Self::k_to_q(k, self.max_size as f64);
268 let q_next = Self::k_to_q(k + self.centroids[idx].weight() / 2.0 + new_weight, self.max_size as f64);
269
270 let current_bound = q_current * total_count;
272 let next_bound = q_next * total_count;
273 let new_total = self.centroids[idx].weight() + new_weight;
274
275 new_total <= (next_bound - current_bound)
276 }
277
278 fn compress(&mut self) {
280 if self.centroids.len() <= self.max_size {
281 return;
282 }
283
284 let mut compressed: Vec<Centroid> = Vec::with_capacity(self.max_size);
285 let total_count = self.count.into_inner();
286
287 let mut k_limit = 1.0;
288 let mut q_limit_times_count = Self::k_to_q(k_limit, self.max_size as f64) * total_count;
289
290 let mut iter = self.centroids.iter();
291 let mut curr = iter.next().unwrap().clone();
292 let mut weight_so_far = curr.weight();
293
294 for centroid in iter {
295 weight_so_far += centroid.weight();
296
297 if weight_so_far <= q_limit_times_count {
298 let combined_weight = curr.weight() + centroid.weight();
300 let combined_mean = (curr.mean() * curr.weight() + centroid.mean() * centroid.weight()) / combined_weight;
301 curr = Centroid::new(combined_mean, combined_weight);
302 } else {
303 compressed.push(curr);
305 curr = centroid.clone();
306 k_limit += 1.0;
307 q_limit_times_count = Self::k_to_q(k_limit, self.max_size as f64) * total_count;
308 }
309 }
310
311 compressed.push(curr);
312 self.centroids = compressed;
313 }
314
315 pub fn add_batch(&mut self, values: &[f64]) {
317 if values.len() > 20 {
318 let sorted: Vec<f64> = {
320 let mut v = values.to_vec();
321 v.sort_by(|a, b| a.partial_cmp(b).unwrap());
322 v
323 };
324 *self = self.merge_sorted(sorted);
325 } else {
326 for &value in values {
328 self.add(value);
329 }
330 }
331 }
332}
333
334impl Default for TDigest {
335 fn default() -> Self {
336 TDigest {
337 centroids: Vec::new(),
338 max_size: 100,
339 sum: OrderedFloat::from(0.0),
340 count: OrderedFloat::from(0.0),
341 max: OrderedFloat::from(std::f64::NAN),
342 min: OrderedFloat::from(std::f64::NAN),
343 }
344 }
345}
346
347impl TDigest {
348 fn k_to_q(k: f64, d: f64) -> f64 {
349 let k_div_d = k / d;
350 if k_div_d >= 0.5 {
351 let base = 1.0 - k_div_d;
352 1.0 - 2.0 * base * base
353 } else {
354 2.0 * k_div_d * k_div_d
355 }
356 }
357
358 fn clamp(v: f64, lo: f64, hi: f64) -> f64 {
359 if v > hi {
360 hi
361 } else if v < lo {
362 lo
363 } else {
364 v
365 }
366 }
367
368 pub fn merge_unsorted(&self, unsorted_values: Vec<f64>) -> TDigest {
369 let mut sorted_values: Vec<OrderedFloat<f64>> = unsorted_values.into_iter().map(OrderedFloat::from).collect();
370 sorted_values.sort();
371 let sorted_values = sorted_values.into_iter().map(|f| f.into_inner()).collect();
372
373 self.merge_sorted(sorted_values)
374 }
375
376 pub fn merge_sorted(&self, sorted_values: Vec<f64>) -> TDigest {
377 if sorted_values.is_empty() {
378 return self.clone();
379 }
380
381 let mut result = TDigest::new_with_size(self.max_size());
382 result.count = OrderedFloat::from(self.count() + (sorted_values.len() as f64));
383
384 let maybe_min = OrderedFloat::from(*sorted_values.first().unwrap());
385 let maybe_max = OrderedFloat::from(*sorted_values.last().unwrap());
386
387 if self.count() > 0.0 {
388 result.min = std::cmp::min(self.min, maybe_min);
389 result.max = std::cmp::max(self.max, maybe_max);
390 } else {
391 result.min = maybe_min;
392 result.max = maybe_max;
393 }
394
395 let mut compressed: Vec<Centroid> = Vec::with_capacity(self.max_size);
396
397 let mut k_limit: f64 = 1.0;
398 let mut q_limit_times_count: f64 = Self::k_to_q(k_limit, self.max_size as f64) * result.count.into_inner();
399 k_limit += 1.0;
400
401 let mut iter_centroids = self.centroids.iter().peekable();
402 let mut iter_sorted_values = sorted_values.iter().peekable();
403
404 let mut curr: Centroid = if let Some(c) = iter_centroids.peek() {
405 let curr = **iter_sorted_values.peek().unwrap();
406 if c.mean() < curr {
407 iter_centroids.next().unwrap().clone()
408 } else {
409 Centroid::new(*iter_sorted_values.next().unwrap(), 1.0)
410 }
411 } else {
412 Centroid::new(*iter_sorted_values.next().unwrap(), 1.0)
413 };
414
415 let mut weight_so_far: f64 = curr.weight();
416
417 let mut sums_to_merge: f64 = 0.0;
418 let mut weights_to_merge: f64 = 0.0;
419
420 while iter_centroids.peek().is_some() || iter_sorted_values.peek().is_some() {
421 let next: Centroid = if let Some(c) = iter_centroids.peek() {
422 if iter_sorted_values.peek().is_none() || c.mean() < **iter_sorted_values.peek().unwrap() {
423 iter_centroids.next().unwrap().clone()
424 } else {
425 Centroid::new(*iter_sorted_values.next().unwrap(), 1.0)
426 }
427 } else {
428 Centroid::new(*iter_sorted_values.next().unwrap(), 1.0)
429 };
430
431 let next_sum: f64 = next.mean() * next.weight();
432 weight_so_far += next.weight();
433
434 if weight_so_far <= q_limit_times_count {
435 sums_to_merge += next_sum;
436 weights_to_merge += next.weight();
437 } else {
438 result.sum = OrderedFloat::from(result.sum.into_inner() + curr.add(sums_to_merge, weights_to_merge));
439 sums_to_merge = 0.0;
440 weights_to_merge = 0.0;
441
442 compressed.push(curr.clone());
443 q_limit_times_count = Self::k_to_q(k_limit, self.max_size as f64) * result.count();
444 k_limit += 1.0;
445 curr = next;
446 }
447 }
448
449 result.sum = OrderedFloat::from(result.sum.into_inner() + curr.add(sums_to_merge, weights_to_merge));
450 compressed.push(curr);
451 compressed.shrink_to_fit();
452 compressed.sort();
453
454 result.centroids = compressed;
455 result
456 }
457
458 fn external_merge(centroids: &mut Vec<Centroid>, first: usize, middle: usize, last: usize) {
459 let mut result: Vec<Centroid> = Vec::with_capacity(centroids.len());
460
461 let mut i = first;
462 let mut j = middle;
463
464 while i < middle && j < last {
465 match centroids[i].cmp(¢roids[j]) {
466 Ordering::Less => {
467 result.push(centroids[i].clone());
468 i += 1;
469 }
470 Ordering::Greater => {
471 result.push(centroids[j].clone());
472 j += 1;
473 }
474 Ordering::Equal => {
475 result.push(centroids[i].clone());
476 i += 1;
477 }
478 }
479 }
480
481 while i < middle {
482 result.push(centroids[i].clone());
483 i += 1;
484 }
485
486 while j < last {
487 result.push(centroids[j].clone());
488 j += 1;
489 }
490
491 i = first;
492 for centroid in result.into_iter() {
493 centroids[i] = centroid;
494 i += 1;
495 }
496 }
497
498 pub fn merge_digests(digests: Vec<TDigest>) -> TDigest {
500 let n_centroids: usize = digests.iter().map(|d| d.centroids.len()).sum();
501 if n_centroids == 0 {
502 return TDigest::default();
503 }
504
505 let max_size = digests.first().unwrap().max_size;
506 let mut centroids: Vec<Centroid> = Vec::with_capacity(n_centroids);
507 let mut starts: Vec<usize> = Vec::with_capacity(digests.len());
508
509 let mut count: f64 = 0.0;
510 let mut min = OrderedFloat::from(std::f64::INFINITY);
511 let mut max = OrderedFloat::from(std::f64::NEG_INFINITY);
512
513 let mut start: usize = 0;
514 for digest in digests.into_iter() {
515 starts.push(start);
516
517 let curr_count: f64 = digest.count();
518 if curr_count > 0.0 {
519 min = std::cmp::min(min, digest.min);
520 max = std::cmp::max(max, digest.max);
521 count += curr_count;
522 for centroid in digest.centroids {
523 centroids.push(centroid);
524 start += 1;
525 }
526 }
527 }
528
529 let mut digests_per_block: usize = 1;
530 while digests_per_block < starts.len() {
531 for i in (0..starts.len()).step_by(digests_per_block * 2) {
532 if i + digests_per_block < starts.len() {
533 let first = starts[i];
534 let middle = starts[i + digests_per_block];
535 let last = if i + 2 * digests_per_block < starts.len() {
536 starts[i + 2 * digests_per_block]
537 } else {
538 centroids.len()
539 };
540
541 debug_assert!(first <= middle && middle <= last);
542 Self::external_merge(&mut centroids, first, middle, last);
543 }
544 }
545
546 digests_per_block *= 2;
547 }
548
549 let mut result = TDigest::new_with_size(max_size);
550 let mut compressed: Vec<Centroid> = Vec::with_capacity(max_size);
551
552 let mut k_limit: f64 = 1.0;
553 let mut q_limit_times_count: f64 = Self::k_to_q(k_limit, max_size as f64) * (count as f64);
554
555 let mut iter_centroids = centroids.iter_mut();
556 let mut curr = iter_centroids.next().unwrap();
557 let mut weight_so_far: f64 = curr.weight();
558 let mut sums_to_merge: f64 = 0.0;
559 let mut weights_to_merge: f64 = 0.0;
560
561 for centroid in iter_centroids {
562 weight_so_far += centroid.weight();
563
564 if weight_so_far <= q_limit_times_count {
565 sums_to_merge += centroid.mean() * centroid.weight();
566 weights_to_merge += centroid.weight();
567 } else {
568 result.sum = OrderedFloat::from(result.sum.into_inner() + curr.add(sums_to_merge, weights_to_merge));
569 sums_to_merge = 0.0;
570 weights_to_merge = 0.0;
571 compressed.push(curr.clone());
572 q_limit_times_count = Self::k_to_q(k_limit, max_size as f64) * (count as f64);
573 k_limit += 1.0;
574 curr = centroid;
575 }
576 }
577
578 result.sum = OrderedFloat::from(result.sum.into_inner() + curr.add(sums_to_merge, weights_to_merge));
579 compressed.push(curr.clone());
580 compressed.shrink_to_fit();
581 compressed.sort();
582
583 result.count = OrderedFloat::from(count as f64);
584 result.min = min;
585 result.max = max;
586 result.centroids = compressed;
587 result
588 }
589
590 pub fn estimate_quantile(&self, q: f64) -> f64 {
592 if self.centroids.is_empty() {
593 return 0.0;
594 }
595
596 let count_: f64 = self.count.into_inner();
597 let rank: f64 = q * count_;
598
599 let mut pos: usize;
600 let mut t: f64;
601 if q > 0.5 {
602 if q >= 1.0 {
603 return self.max();
604 }
605
606 pos = 0;
607 t = count_;
608
609 for (k, centroid) in self.centroids.iter().enumerate().rev() {
610 t -= centroid.weight();
611
612 if rank >= t {
613 pos = k;
614 break;
615 }
616 }
617 } else {
618 if q <= 0.0 {
619 return self.min();
620 }
621
622 pos = self.centroids.len() - 1;
623 t = 0.0;
624
625 for (k, centroid) in self.centroids.iter().enumerate() {
626 if rank < t + centroid.weight() {
627 pos = k;
628 break;
629 }
630
631 t += centroid.weight();
632 }
633 }
634
635 let mut delta = 0.0;
636 let mut min: f64 = self.min.into_inner();
637 let mut max: f64 = self.max.into_inner();
638
639 if self.centroids.len() > 1 {
640 if pos == 0 {
641 delta = self.centroids[pos + 1].mean() - self.centroids[pos].mean();
642 max = self.centroids[pos + 1].mean();
643 } else if pos == (self.centroids.len() - 1) {
644 delta = self.centroids[pos].mean() - self.centroids[pos - 1].mean();
645 min = self.centroids[pos - 1].mean();
646 } else {
647 delta = (self.centroids[pos + 1].mean() - self.centroids[pos - 1].mean()) / 2.0;
648 min = self.centroids[pos - 1].mean();
649 max = self.centroids[pos + 1].mean();
650 }
651 }
652
653 let value = self.centroids[pos].mean() + ((rank - t) / self.centroids[pos].weight() - 0.5) * delta;
654 Self::clamp(value, min, max)
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661
662 #[test]
663 fn test_centroid_addition_regression() {
664 let vals = vec![1.0, 1.0, 1.0, 2.0, 1.0, 1.0];
667 let mut t = TDigest::new_with_size(10);
668
669 for v in vals {
670 t = t.merge_unsorted(vec![v]);
671 }
672
673 let ans = t.estimate_quantile(0.5);
674 let expected: f64 = 1.0;
675 let percentage: f64 = (expected - ans).abs() / expected;
676 assert!(percentage < 0.01);
677
678 let ans = t.estimate_quantile(0.95);
679 let expected: f64 = 2.0;
680 let percentage: f64 = (expected - ans).abs() / expected;
681 assert!(percentage < 0.01);
682 }
683
684 #[test]
685 fn test_merge_sorted_against_uniform_distro() {
686 let t = TDigest::new_with_size(100);
687 let values: Vec<f64> = (1..=1_000_000).map(f64::from).collect();
688
689 let t = t.merge_sorted(values);
690
691 let ans = t.estimate_quantile(1.0);
692 let expected: f64 = 1_000_000.0;
693
694 let percentage: f64 = (expected - ans).abs() / expected;
695 assert!(percentage < 0.01);
696
697 let ans = t.estimate_quantile(0.99);
698 let expected: f64 = 990_000.0;
699
700 let percentage: f64 = (expected - ans).abs() / expected;
701 assert!(percentage < 0.01);
702
703 let ans = t.estimate_quantile(0.01);
704 let expected: f64 = 10_000.0;
705
706 let percentage: f64 = (expected - ans).abs() / expected;
707 assert!(percentage < 0.01);
708
709 let ans = t.estimate_quantile(0.0);
710 let expected: f64 = 1.0;
711
712 let percentage: f64 = (expected - ans).abs() / expected;
713 assert!(percentage < 0.01);
714
715 let ans = t.estimate_quantile(0.5);
716 let expected: f64 = 500_000.0;
717
718 let percentage: f64 = (expected - ans).abs() / expected;
719 assert!(percentage < 0.01);
720 }
721
722 #[test]
723 fn test_merge_unsorted_against_uniform_distro() {
724 let t = TDigest::new_with_size(100);
725 let values: Vec<f64> = (1..=1_000_000).map(f64::from).collect();
726
727 let t = t.merge_unsorted(values);
728
729 let ans = t.estimate_quantile(1.0);
730 let expected: f64 = 1_000_000.0;
731
732 let percentage: f64 = (expected - ans).abs() / expected;
733 assert!(percentage < 0.01);
734
735 let ans = t.estimate_quantile(0.99);
736 let expected: f64 = 990_000.0;
737
738 let percentage: f64 = (expected - ans).abs() / expected;
739 assert!(percentage < 0.01);
740
741 let ans = t.estimate_quantile(0.01);
742 let expected: f64 = 10_000.0;
743
744 let percentage: f64 = (expected - ans).abs() / expected;
745 assert!(percentage < 0.01);
746
747 let ans = t.estimate_quantile(0.0);
748 let expected: f64 = 1.0;
749
750 let percentage: f64 = (expected - ans).abs() / expected;
751 assert!(percentage < 0.01);
752
753 let ans = t.estimate_quantile(0.5);
754 let expected: f64 = 500_000.0;
755
756 let percentage: f64 = (expected - ans).abs() / expected;
757 assert!(percentage < 0.01);
758 }
759
760 #[test]
761 fn test_merge_sorted_against_skewed_distro() {
762 let t = TDigest::new_with_size(100);
763 let mut values: Vec<f64> = (1..=600_000).map(f64::from).collect();
764 for _ in 0..400_000 {
765 values.push(1_000_000.0);
766 }
767
768 let t = t.merge_sorted(values);
769
770 let ans = t.estimate_quantile(0.99);
771 let expected: f64 = 1_000_000.0;
772 let percentage: f64 = (expected - ans).abs() / expected;
773 assert!(percentage < 0.01);
774
775 let ans = t.estimate_quantile(0.01);
776 let expected: f64 = 10_000.0;
777
778 let percentage: f64 = (expected - ans).abs() / expected;
779 assert!(percentage < 0.01);
780
781 let ans = t.estimate_quantile(0.5);
782 let expected: f64 = 500_000.0;
783
784 let percentage: f64 = (expected - ans).abs() / expected;
785 assert!(percentage < 0.01);
786 }
787
788 #[test]
789 fn test_merge_unsorted_against_skewed_distro() {
790 let t = TDigest::new_with_size(100);
791 let mut values: Vec<f64> = (1..=600_000).map(f64::from).collect();
792 for _ in 0..400_000 {
793 values.push(1_000_000.0);
794 }
795
796 let t = t.merge_unsorted(values);
797
798 let ans = t.estimate_quantile(0.99);
799 let expected: f64 = 1_000_000.0;
800 let percentage: f64 = (expected - ans).abs() / expected;
801 assert!(percentage < 0.01);
802
803 let ans = t.estimate_quantile(0.01);
804 let expected: f64 = 10_000.0;
805
806 let percentage: f64 = (expected - ans).abs() / expected;
807 assert!(percentage < 0.01);
808
809 let ans = t.estimate_quantile(0.5);
810 let expected: f64 = 500_000.0;
811
812 let percentage: f64 = (expected - ans).abs() / expected;
813 assert!(percentage < 0.01);
814 }
815
816 #[test]
817 fn test_merge_digests() {
818 let mut digests: Vec<TDigest> = Vec::new();
819
820 for _ in 1..=100 {
821 let t = TDigest::new_with_size(100);
822 let values: Vec<f64> = (1..=1_000).map(f64::from).collect();
823 let t = t.merge_sorted(values);
824 digests.push(t)
825 }
826
827 let t = TDigest::merge_digests(digests);
828
829 let ans = t.estimate_quantile(1.0);
830 let expected: f64 = 1000.0;
831
832 let percentage: f64 = (expected - ans).abs() / expected;
833 assert!(percentage < 0.01);
834
835 let ans = t.estimate_quantile(0.99);
836 let expected: f64 = 990.0;
837
838 let percentage: f64 = (expected - ans).abs() / expected;
839 assert!(percentage < 0.01);
840
841 let ans = t.estimate_quantile(0.01);
842 let expected: f64 = 10.0;
843
844 let percentage: f64 = (expected - ans).abs() / expected;
845 assert!(percentage < 0.2);
846
847 let ans = t.estimate_quantile(0.0);
848 let expected: f64 = 1.0;
849
850 let percentage: f64 = (expected - ans).abs() / expected;
851 assert!(percentage < 0.01);
852
853 let ans = t.estimate_quantile(0.5);
854 let expected: f64 = 500.0;
855
856 let percentage: f64 = (expected - ans).abs() / expected;
857 assert!(percentage < 0.01);
858 }
859}