1use std::error;
2use std::fmt;
3
4use crate::config::Config;
5use crate::store::Store;
6
7#[cfg(feature = "use_serde")]
8use serde::{Deserialize, Serialize};
9
10#[cfg(feature = "use_rkyv")]
11use rkyv::{Deserialize, Serialize, Archive};
12
13type Result<T> = std::result::Result<T, DDSketchError>;
14
15#[derive(Debug, Clone)]
19pub enum DDSketchError {
20 Quantile,
21 Merge,
22}
23impl fmt::Display for DDSketchError {
24 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25 match self {
26 DDSketchError::Quantile => {
27 write!(f, "Invalid quantile, must be between 0 and 1 (inclusive)")
28 }
29 DDSketchError::Merge => write!(f, "Can not merge sketches with different configs"),
30 }
31 }
32}
33impl error::Error for DDSketchError {
34 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
35 None
37 }
38}
39
40#[derive(Clone)]
42#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
43#[cfg_attr(feature = "use_rkyv", derive(Serialize, Deserialize, Archive))]
44pub struct DDSketch {
45 config: Config,
46 store: Store,
47 negative_store: Store,
48 min: f64,
49 max: f64,
50 sum: f64,
51 zero_count: u64,
52}
53
54impl Default for DDSketch {
55 fn default() -> Self {
56 Self::new(Default::default())
57 }
58}
59
60impl DDSketch {
62 pub fn new(config: Config) -> Self {
64 DDSketch {
65 config,
66 store: Store::new(config.max_num_bins as usize),
67 negative_store: Store::new(config.max_num_bins as usize),
68 min: f64::INFINITY,
69 max: f64::NEG_INFINITY,
70 sum: 0.0,
71 zero_count: 0,
72 }
73 }
74
75 pub fn add(&mut self, v: f64) {
77 if v > self.config.min_possible() {
78 let key = self.config.key(v);
79 self.store.add(key);
80 } else if v < -self.config.min_possible() {
81 let key = self.config.key(-v);
82 self.negative_store.add(key);
83 } else {
84 self.zero_count += 1;
85 }
86
87 if v < self.min {
88 self.min = v;
89 }
90 if self.max < v {
91 self.max = v;
92 }
93 self.sum += v;
94 }
95
96 pub fn quantile(&self, q: f64) -> Result<Option<f64>> {
101 if q < 0.0 || q > 1.0 {
102 return Err(DDSketchError::Quantile);
103 }
104
105 if self.empty() {
106 return Ok(None);
107 }
108
109 if q == 0.0 {
110 return Ok(Some(self.min));
111 } else if q == 1.0 {
112 return Ok(Some(self.max));
113 }
114
115 let rank = (q * (self.count() as f64 - 1.0)) as u64;
116 let quantile;
117 if rank < self.negative_store.count() {
118 let reversed_rank = self.negative_store.count() - rank - 1;
119 let key = self.negative_store.key_at_rank(reversed_rank);
120 quantile = -self.config.value(key);
121 } else if rank < self.zero_count + self.negative_store.count() {
122 quantile = 0.0;
123 } else {
124 let key = self
125 .store
126 .key_at_rank(rank - self.zero_count - self.negative_store.count());
127 quantile = self.config.value(key);
128 }
129
130 Ok(Some(quantile))
131 }
132
133 pub fn min(&self) -> Option<f64> {
135 if self.empty() {
136 None
137 } else {
138 Some(self.min)
139 }
140 }
141
142 pub fn max(&self) -> Option<f64> {
144 if self.empty() {
145 None
146 } else {
147 Some(self.max)
148 }
149 }
150
151 pub fn sum(&self) -> Option<f64> {
153 if self.empty() {
154 None
155 } else {
156 Some(self.sum)
157 }
158 }
159
160 pub fn count(&self) -> usize {
162 (self.store.count() + self.zero_count + self.negative_store.count()) as usize
163 }
164
165 pub fn length(&self) -> usize {
168 self.store.length() as usize + self.negative_store.length() as usize
169 }
170
171 pub fn merge(&mut self, o: &DDSketch) -> Result<()> {
174 if self.config != o.config {
175 return Err(DDSketchError::Merge);
176 }
177
178 let was_empty = self.store.count() == 0;
179
180 self.store.merge(&o.store);
182 self.negative_store.merge(&o.negative_store);
183 self.zero_count += o.zero_count;
184
185 if was_empty {
188 self.min = o.min;
189 self.max = o.max;
190 } else if o.store.count() > 0 {
191 if o.min < self.min {
192 self.min = o.min
193 }
194 if o.max > self.max {
195 self.max = o.max;
196 }
197 }
198 self.sum += o.sum;
199
200 Ok(())
201 }
202
203 fn empty(&self) -> bool {
204 self.count() == 0
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use approx::assert_relative_eq;
211
212 use crate::Config;
213 use crate::DDSketch;
214
215 #[test]
216 fn test_add_zero() {
217 let alpha = 0.01;
218 let c = Config::new(alpha, 2048, 10e-9);
219 let mut dd = DDSketch::new(c);
220 dd.add(0.0);
221 }
222
223 #[test]
224 fn test_quartiles() {
225 let alpha = 0.01;
226 let c = Config::new(alpha, 2048, 10e-9);
227 let mut dd = DDSketch::new(c);
228
229 for i in 1..5 {
231 dd.add(i as f64);
232 }
233
234 let test_cases = vec![
237 (0.0, 1.0),
238 (0.25, 1.0),
239 (0.33, 1.0),
240 (0.34, 2.0),
241 (0.5, 2.0),
242 (0.66, 2.0),
243 (0.67, 3.0),
244 (0.75, 3.0),
245 (0.99, 3.0),
246 (1.0, 4.0),
247 ];
248
249 for (q, val) in test_cases {
250 assert_relative_eq!(dd.quantile(q).unwrap().unwrap(), val, max_relative = alpha);
251 }
252 }
253
254 #[test]
255 fn test_neg_quartiles() {
256 let alpha = 0.01;
257 let c = Config::new(alpha, 2048, 10e-9);
258 let mut dd = DDSketch::new(c);
259
260 for i in 1..5 {
262 dd.add(-i as f64);
263 }
264
265 let test_cases = vec![
266 (0.0, -4.0),
267 (0.25, -4.0),
268 (0.5, -3.0),
269 (0.75, -2.0),
270 (1.0, -1.0),
271 ];
272
273 for (q, val) in test_cases {
274 assert_relative_eq!(dd.quantile(q).unwrap().unwrap(), val, max_relative = alpha);
275 }
276 }
277
278 #[test]
279 fn test_simple_quantile() {
280 let c = Config::defaults();
281 let mut dd = DDSketch::new(c);
282
283 for i in 1..101 {
284 dd.add(i as f64);
285 }
286
287 assert_eq!(dd.quantile(0.95).unwrap().unwrap().ceil(), 95.0);
288
289 assert!(dd.quantile(-1.01).is_err());
290 assert!(dd.quantile(1.01).is_err());
291 }
292
293 #[test]
294 fn test_empty_sketch() {
295 let c = Config::defaults();
296 let dd = DDSketch::new(c);
297
298 assert_eq!(dd.quantile(0.98).unwrap(), None);
299 assert_eq!(dd.max(), None);
300 assert_eq!(dd.min(), None);
301 assert_eq!(dd.sum(), None);
302 assert_eq!(dd.count(), 0);
303
304 assert!(dd.quantile(1.01).is_err());
305 }
306
307 #[test]
308 fn test_basic_histogram_data() {
309 let values = &[
310 0.754225035,
311 0.752900282,
312 0.752812246,
313 0.752602367,
314 0.754310155,
315 0.753525981,
316 0.752981082,
317 0.752715536,
318 0.751667941,
319 0.755079054,
320 0.753528150,
321 0.755188464,
322 0.752508723,
323 0.750064549,
324 0.753960428,
325 0.751139298,
326 0.752523560,
327 0.753253428,
328 0.753498342,
329 0.751858358,
330 0.752104636,
331 0.753841300,
332 0.754467374,
333 0.753814334,
334 0.750881719,
335 0.753182556,
336 0.752576884,
337 0.753945708,
338 0.753571911,
339 0.752314573,
340 0.752586651,
341 ];
342
343 let c = Config::defaults();
344 let mut dd = DDSketch::new(c);
345
346 for value in values {
347 dd.add(*value);
348 }
349
350 assert_eq!(dd.max(), Some(0.755188464));
351 assert_eq!(dd.min(), Some(0.750064549));
352 assert_eq!(dd.count(), 31);
353 assert_eq!(dd.sum(), Some(23.343630625000003));
354
355 assert!(dd.quantile(0.25).unwrap().is_some());
356 assert!(dd.quantile(0.5).unwrap().is_some());
357 assert!(dd.quantile(0.75).unwrap().is_some());
358 }
359
360 #[test]
361 fn test_length() {
362 let mut dd = DDSketch::default();
363 assert_eq!(dd.length(), 0);
364
365 dd.add(1.0);
366 assert_eq!(dd.length(), 128);
367 dd.add(2.0);
368 dd.add(3.0);
369 assert_eq!(dd.length(), 128);
370
371 dd.add(-1.0);
372 assert_eq!(dd.length(), 256);
373 dd.add(-2.0);
374 dd.add(-3.0);
375 assert_eq!(dd.length(), 256);
376 }
377}