use crate::utils::gen_prime;
use rand::Rng;
pub const MAX_UNIVERSE_SIZE: u64 = u64::MAX;
#[derive(Debug, Clone, Copy)]
pub enum ParamError {
InvalidEpsilon(f64),
InvalidMaxInterval { max_range_interval: u64, input: u64 },
Overflow,
}
#[derive(Debug, Clone, Copy)]
pub struct PairwiseIndependentHasher {
slope: u64,
intercept: u64,
large_prime: u64,
reduced_universe_size: u64,
}
impl PairwiseIndependentHasher {
pub fn new(num_elements: usize, epsilon: f64, max_interval: u64) -> Result<Self, ParamError> {
if epsilon <= 0.0 || 1.0 <= epsilon {
return Err(ParamError::InvalidEpsilon(epsilon));
}
let max_range_interval = Self::max_range_interval(MAX_UNIVERSE_SIZE, num_elements, epsilon);
if max_interval > max_range_interval {
return Err(ParamError::InvalidMaxInterval {
max_range_interval,
input: max_interval,
});
}
let upper = (num_elements as u64)
.checked_mul(max_interval)
.ok_or(ParamError::Overflow)?;
let lower = (1.0 / epsilon).floor() as u64;
let reduced_universe_size = upper.checked_mul(lower).ok_or(ParamError::Overflow)?;
let mut rng = rand::rng();
let p = gen_prime(&mut rng, 1 + reduced_universe_size..MAX_UNIVERSE_SIZE);
let c1 = rng.random_range(1..p);
let c2 = rng.random_range(0..p);
Ok(Self {
slope: c1,
intercept: c2,
large_prime: p,
reduced_universe_size,
})
}
pub fn epsilon_with_space_budget(
bits_per_key: u8,
max_interval: u64,
) -> Result<f64, ParamError> {
if bits_per_key <= 2 || bits_per_key > 64 {
Err(ParamError::Overflow)
} else {
Ok(max_interval as f64 / (1 << (bits_per_key - 2)) as f64)
}
}
pub fn new_with_space_budget(
num_elements: usize,
bits_per_key: u8,
max_interval: u64,
) -> Result<Self, ParamError> {
let epsilon = Self::epsilon_with_space_budget(bits_per_key, max_interval)?;
Self::new(num_elements, epsilon, max_interval)
}
pub fn new_with_reduced(reduced_universe_size: u64) -> Self {
let mut rng = rand::rng();
let p = gen_prime(&mut rng, 1 + reduced_universe_size..MAX_UNIVERSE_SIZE);
let c1 = rng.random_range(1..p);
let c2 = rng.random_range(0..p);
Self {
slope: c1,
intercept: c2,
large_prime: p,
reduced_universe_size,
}
}
pub fn hash(&self, x: u64) -> u64 {
(self.slope.wrapping_mul(x).wrapping_add(self.intercept) % self.large_prime)
% self.reduced_universe_size
}
pub fn local_hash(&self, x: u64) -> u64 {
let inner = x / self.reduced_universe_size;
let q = self.hash(inner);
q.wrapping_add(x) % self.reduced_universe_size
}
pub fn reduced_universe_size(&self) -> u64 {
self.reduced_universe_size
}
pub fn false_positive_rate(&self, num_elements: usize, max_interval: u64) -> f64 {
let nl = (num_elements as u64 * max_interval) as f64;
let r = self.reduced_universe_size as f64;
nl / r
}
fn max_range_interval(universe_size: u64, num_elements: usize, epsilon: f64) -> u64 {
assert!(
0.0 < epsilon && epsilon < 1.0,
"epsilon must be between 0.0 and 1.0"
);
((universe_size as f64) * epsilon) as u64 / num_elements as u64
}
}