1use crate::Error;
2use core::ops::RangeInclusive;
3
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
60#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
61pub struct Config {
62    max: u64,
63    grouping_power: u8,
64    max_value_power: u8,
65    cutoff_power: u8,
66    cutoff_value: u64,
67    lower_bin_count: u32,
68    upper_bin_divisions: u32,
69    upper_bin_count: u32,
70}
71
72impl Config {
73    pub const fn new(grouping_power: u8, max_value_power: u8) -> Result<Self, Error> {
77        if max_value_power > 64 {
79            return Err(Error::MaxPowerTooHigh);
80        }
81
82        if grouping_power >= max_value_power {
84            return Err(Error::MaxPowerTooLow);
85        }
86
87        let cutoff_power = grouping_power + 1;
103        let cutoff_value = 2_u64.pow(cutoff_power as u32);
104        let lower_bin_width = 2_u32.pow(0);
105        let upper_bin_divisions = 2_u32.pow(grouping_power as u32);
106
107        let max = if max_value_power == 64 {
108            u64::MAX
109        } else {
110            2_u64.pow(max_value_power as u32)
111        };
112
113        let lower_bin_count = (cutoff_value / lower_bin_width as u64) as u32;
114        let upper_bin_count = (max_value_power - cutoff_power) as u32 * upper_bin_divisions;
115
116        Ok(Self {
117            max,
118            grouping_power,
119            max_value_power,
120            cutoff_power,
121            cutoff_value,
122            lower_bin_count,
123            upper_bin_divisions,
124            upper_bin_count,
125        })
126    }
127
128    pub const fn grouping_power(&self) -> u8 {
130        self.grouping_power
131    }
132
133    pub const fn max_value_power(&self) -> u8 {
135        self.max_value_power
136    }
137
138    pub fn error(&self) -> f64 {
143        match self.grouping_power == self.max_value_power - 1 {
144            true => 0.0,
145            false => 100.0 / 2_u64.pow(self.grouping_power as u32) as f64,
146        }
147    }
148
149    pub const fn total_buckets(&self) -> usize {
151        (self.lower_bin_count + self.upper_bin_count) as usize
152    }
153
154    pub(crate) fn value_to_index(&self, value: u64) -> Result<usize, Error> {
157        if value < self.cutoff_value {
158            return Ok(value as usize);
159        }
160
161        if value > self.max {
162            return Err(Error::OutOfRange);
163        }
164
165        let power = 63 - value.leading_zeros();
166        let log_bin = power - self.cutoff_power as u32;
167        let offset = (value - (1 << power)) >> (power - self.grouping_power as u32);
168
169        Ok((self.lower_bin_count + log_bin * self.upper_bin_divisions + offset as u32) as usize)
170    }
171
172    pub(crate) fn index_to_lower_bound(&self, index: usize) -> u64 {
174        let g = index as u64 >> self.grouping_power;
175        let h = index as u64 - g * (1 << self.grouping_power);
176
177        if g < 1 {
178            h
179        } else {
180            (1 << (self.grouping_power as u64 + g - 1)) + (1 << (g - 1)) * h
181        }
182    }
183
184    pub(crate) fn index_to_upper_bound(&self, index: usize) -> u64 {
186        if index as u32 == self.lower_bin_count + self.upper_bin_count - 1 {
187            return self.max;
188        }
189        let g = index as u64 >> self.grouping_power;
190        let h = index as u64 - g * (1 << self.grouping_power) + 1;
191
192        if g < 1 {
193            h - 1
194        } else {
195            (1 << (self.grouping_power as u64 + g - 1)) + (1 << (g - 1)) * h - 1
196        }
197    }
198
199    pub(crate) fn index_to_range(&self, index: usize) -> RangeInclusive<u64> {
201        self.index_to_lower_bound(index)..=self.index_to_upper_bound(index)
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[cfg(target_pointer_width = "64")]
210    #[test]
211    fn sizes() {
212        assert_eq!(std::mem::size_of::<Config>(), 32);
213    }
214
215    #[test]
216    fn total_buckets() {
218        let config = Config::new(2, 64).unwrap();
219        assert_eq!(config.total_buckets(), 252);
220
221        let config = Config::new(7, 64).unwrap();
222        assert_eq!(config.total_buckets(), 7424);
223
224        let config = Config::new(14, 64).unwrap();
225        assert_eq!(config.total_buckets(), 835_584);
226
227        let config = Config::new(2, 4).unwrap();
228        assert_eq!(config.total_buckets(), 12);
229    }
230
231    #[test]
232    fn value_to_idx() {
234        let config = Config::new(7, 64).unwrap();
235        assert_eq!(config.value_to_index(0), Ok(0));
236        assert_eq!(config.value_to_index(1), Ok(1));
237        assert_eq!(config.value_to_index(256), Ok(256));
238        assert_eq!(config.value_to_index(257), Ok(256));
239        assert_eq!(config.value_to_index(258), Ok(257));
240        assert_eq!(config.value_to_index(512), Ok(384));
241        assert_eq!(config.value_to_index(515), Ok(384));
242        assert_eq!(config.value_to_index(516), Ok(385));
243        assert_eq!(config.value_to_index(1024), Ok(512));
244        assert_eq!(config.value_to_index(1031), Ok(512));
245        assert_eq!(config.value_to_index(1032), Ok(513));
246        assert_eq!(config.value_to_index(u64::MAX - 1), Ok(7423));
247        assert_eq!(config.value_to_index(u64::MAX), Ok(7423));
248    }
249
250    #[test]
251    fn idx_to_lower_bound() {
253        let config = Config::new(7, 64).unwrap();
254        assert_eq!(config.index_to_lower_bound(0), 0);
255        assert_eq!(config.index_to_lower_bound(1), 1);
256        assert_eq!(config.index_to_lower_bound(256), 256);
257        assert_eq!(config.index_to_lower_bound(384), 512);
258        assert_eq!(config.index_to_lower_bound(512), 1024);
259        assert_eq!(
260            config.index_to_lower_bound(7423),
261            18_374_686_479_671_623_680
262        );
263    }
264
265    #[test]
266    fn idx_to_upper_bound() {
268        let config = Config::new(7, 64).unwrap();
269        assert_eq!(config.index_to_upper_bound(0), 0);
270        assert_eq!(config.index_to_upper_bound(1), 1);
271        assert_eq!(config.index_to_upper_bound(256), 257);
272        assert_eq!(config.index_to_upper_bound(384), 515);
273        assert_eq!(config.index_to_upper_bound(512), 1031);
274        assert_eq!(config.index_to_upper_bound(7423), u64::MAX);
275    }
276
277    #[test]
278    fn idx_to_range() {
280        let config = Config::new(7, 64).unwrap();
281        assert_eq!(config.index_to_range(0), 0..=0);
282        assert_eq!(config.index_to_range(1), 1..=1);
283        assert_eq!(config.index_to_range(256), 256..=257);
284        assert_eq!(config.index_to_range(384), 512..=515);
285        assert_eq!(config.index_to_range(512), 1024..=1031);
286        assert_eq!(
287            config.index_to_range(7423),
288            18_374_686_479_671_623_680..=u64::MAX
289        );
290    }
291}