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}