use crate::Error;
use core::ops::RangeInclusive;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Config {
max: u64,
grouping_power: u8,
max_value_power: u8,
cutoff_power: u8,
cutoff_value: u64,
lower_bin_count: u32,
upper_bin_divisions: u32,
upper_bin_count: u32,
}
impl Config {
pub const fn new(grouping_power: u8, max_value_power: u8) -> Result<Self, Error> {
if max_value_power > 64 {
return Err(Error::MaxPowerTooHigh);
}
if grouping_power >= max_value_power {
return Err(Error::MaxPowerTooLow);
}
let cutoff_power = grouping_power + 1;
let cutoff_value = 2_u64.pow(cutoff_power as u32);
let upper_bin_divisions = 2_u32.pow(grouping_power as u32);
let max = if max_value_power == 64 {
u64::MAX
} else {
2_u64.pow(max_value_power as u32) - 1
};
let lower_bin_count = cutoff_value as u32;
let upper_bin_count = (max_value_power - cutoff_power) as u32 * upper_bin_divisions;
Ok(Self {
max,
grouping_power,
max_value_power,
cutoff_power,
cutoff_value,
lower_bin_count,
upper_bin_divisions,
upper_bin_count,
})
}
pub const fn grouping_power(&self) -> u8 {
self.grouping_power
}
pub const fn max_value_power(&self) -> u8 {
self.max_value_power
}
pub fn error(&self) -> f64 {
match self.grouping_power == self.max_value_power - 1 {
true => 0.0,
false => 100.0 / 2_u64.pow(self.grouping_power as u32) as f64,
}
}
pub const fn total_buckets(&self) -> usize {
(self.lower_bin_count + self.upper_bin_count) as usize
}
pub(crate) fn value_to_index(&self, value: u64) -> Result<usize, Error> {
if value < self.cutoff_value {
return Ok(value as usize);
}
if value > self.max {
return Err(Error::OutOfRange);
}
let power = 63 - value.leading_zeros();
let log_bin = power - self.cutoff_power as u32;
let offset = (value - (1 << power)) >> (power - self.grouping_power as u32);
Ok((self.lower_bin_count + log_bin * self.upper_bin_divisions + offset as u32) as usize)
}
pub(crate) fn index_to_lower_bound(&self, index: usize) -> u64 {
let g = index as u64 >> self.grouping_power;
let h = index as u64 - g * (1 << self.grouping_power);
if g < 1 {
h
} else {
(1 << (self.grouping_power as u64 + g - 1)) + (1 << (g - 1)) * h
}
}
pub(crate) fn index_to_upper_bound(&self, index: usize) -> u64 {
if index as u32 == self.lower_bin_count + self.upper_bin_count - 1 {
return self.max;
}
let g = index as u64 >> self.grouping_power;
let h = index as u64 - g * (1 << self.grouping_power) + 1;
if g < 1 {
h - 1
} else {
(1 << (self.grouping_power as u64 + g - 1)) + (1 << (g - 1)) * h - 1
}
}
pub(crate) fn index_to_range(&self, index: usize) -> RangeInclusive<u64> {
self.index_to_lower_bound(index)..=self.index_to_upper_bound(index)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(target_pointer_width = "64")]
#[test]
fn sizes() {
assert_eq!(std::mem::size_of::<Config>(), 32);
}
#[test]
fn total_buckets() {
let config = Config::new(2, 64).unwrap();
assert_eq!(config.total_buckets(), 252);
let config = Config::new(7, 64).unwrap();
assert_eq!(config.total_buckets(), 7424);
let config = Config::new(14, 64).unwrap();
assert_eq!(config.total_buckets(), 835_584);
let config = Config::new(2, 4).unwrap();
assert_eq!(config.total_buckets(), 12);
}
#[test]
fn value_to_idx() {
let config = Config::new(7, 64).unwrap();
assert_eq!(config.value_to_index(0), Ok(0));
assert_eq!(config.value_to_index(1), Ok(1));
assert_eq!(config.value_to_index(256), Ok(256));
assert_eq!(config.value_to_index(257), Ok(256));
assert_eq!(config.value_to_index(258), Ok(257));
assert_eq!(config.value_to_index(512), Ok(384));
assert_eq!(config.value_to_index(515), Ok(384));
assert_eq!(config.value_to_index(516), Ok(385));
assert_eq!(config.value_to_index(1024), Ok(512));
assert_eq!(config.value_to_index(1031), Ok(512));
assert_eq!(config.value_to_index(1032), Ok(513));
assert_eq!(config.value_to_index(u64::MAX - 1), Ok(7423));
assert_eq!(config.value_to_index(u64::MAX), Ok(7423));
}
#[test]
fn idx_to_lower_bound() {
let config = Config::new(7, 64).unwrap();
assert_eq!(config.index_to_lower_bound(0), 0);
assert_eq!(config.index_to_lower_bound(1), 1);
assert_eq!(config.index_to_lower_bound(256), 256);
assert_eq!(config.index_to_lower_bound(384), 512);
assert_eq!(config.index_to_lower_bound(512), 1024);
assert_eq!(
config.index_to_lower_bound(7423),
18_374_686_479_671_623_680
);
}
#[test]
fn idx_to_upper_bound() {
let config = Config::new(7, 64).unwrap();
assert_eq!(config.index_to_upper_bound(0), 0);
assert_eq!(config.index_to_upper_bound(1), 1);
assert_eq!(config.index_to_upper_bound(256), 257);
assert_eq!(config.index_to_upper_bound(384), 515);
assert_eq!(config.index_to_upper_bound(512), 1031);
assert_eq!(config.index_to_upper_bound(7423), u64::MAX);
}
#[test]
fn idx_to_range() {
let config = Config::new(7, 64).unwrap();
assert_eq!(config.index_to_range(0), 0..=0);
assert_eq!(config.index_to_range(1), 1..=1);
assert_eq!(config.index_to_range(256), 256..=257);
assert_eq!(config.index_to_range(384), 512..=515);
assert_eq!(config.index_to_range(512), 1024..=1031);
assert_eq!(
config.index_to_range(7423),
18_374_686_479_671_623_680..=u64::MAX
);
}
}