pub struct BloomFilter<const BITS: usize> {
bits: Vec<u64>,
num_hashes: usize,
}
impl<const BITS: usize> BloomFilter<BITS> {
const WORDS: usize = BITS.div_ceil(64);
pub fn with_hashes(num_hashes: usize) -> Self {
BloomFilter {
bits: vec![0u64; Self::WORDS],
num_hashes,
}
}
pub fn optimal(n: usize) -> Self {
let bits = BITS as f64;
let k = if n > 0 {
(bits / n as f64 * std::f64::consts::LN_2).round() as usize
} else {
1
};
let k = k.max(1);
Self::with_hashes(k)
}
#[inline]
fn double_hash(key: u64) -> (u64, u64) {
#[cfg(feature = "bcinr")]
{
let h1 = crate::bcinr_compat::sketch::fnv1a_64(&key.to_le_bytes());
let h2 = crate::bcinr_compat::sketch::fnv1a_64(&(h1.rotate_left(17)).to_le_bytes());
(h1, h2)
}
#[cfg(not(feature = "bcinr"))]
{
let h1 = key
.wrapping_mul(0x517cc1b727220a95)
.wrapping_add(0x6c62272e07bb0142);
let h2 = h1.rotate_left(17).wrapping_mul(0x9e3779b97f4a7c15);
(h1, h2)
}
}
#[inline]
fn nth_hash(h1: u64, h2: u64, i: usize) -> usize {
(h1.wrapping_add((i as u64).wrapping_mul(h2))) as usize % BITS
}
#[inline]
pub fn insert(&mut self, hash: u64) {
let (h1, h2) = Self::double_hash(hash);
for i in 0..self.num_hashes {
let bit = Self::nth_hash(h1, h2, i);
let word = bit / 64;
let bit_in_word = bit % 64;
#[cfg(feature = "bcinr")]
{
let word =
bcinr::mask::select_u64((word < Self::WORDS) as u64, word as u64, 0) as usize;
self.bits[word] |= 1u64 << bit_in_word;
}
#[cfg(not(feature = "bcinr"))]
{
if word < Self::WORDS {
self.bits[word] |= 1u64 << bit_in_word;
}
}
}
}
#[inline]
pub fn contains(&self, hash: u64) -> bool {
let (h1, h2) = Self::double_hash(hash);
#[cfg(feature = "bcinr")]
let mut possible = 1u64;
for i in 0..self.num_hashes {
let bit = Self::nth_hash(h1, h2, i);
let word = bit / 64;
let bit_in_word = bit % 64;
let exists =
(word < Self::WORDS && (self.bits[word] & (1u64 << bit_in_word)) != 0) as u64;
#[cfg(feature = "bcinr")]
{
possible &= exists;
}
#[cfg(not(feature = "bcinr"))]
{
if exists == 0 {
return false;
}
}
}
#[cfg(feature = "bcinr")]
return possible != 0;
#[cfg(not(feature = "bcinr"))]
return true;
}
pub fn clear(&mut self) {
for word in self.bits.iter_mut() {
*word = 0;
}
}
pub fn memory_bytes(&self) -> usize {
Self::WORDS * 8
}
pub fn num_hashes(&self) -> usize {
self.num_hashes
}
}
impl<const BITS: usize> Default for BloomFilter<BITS> {
fn default() -> Self {
Self::with_hashes(3)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_filter() {
let bloom: BloomFilter<16384> = BloomFilter::with_hashes(3);
assert!(!bloom.contains(42));
}
#[test]
fn test_insert_and_contains() {
let mut bloom: BloomFilter<16384> = BloomFilter::with_hashes(3);
bloom.insert(42);
assert!(bloom.contains(42));
}
#[test]
fn test_no_false_negatives() {
let mut bloom: BloomFilter<16384> = BloomFilter::with_hashes(3);
for i in 0..5000u64 {
bloom.insert(i);
}
for i in 0..5000u64 {
assert!(bloom.contains(i), "False negative for key {}", i);
}
}
#[test]
fn test_false_positive_rate() {
let mut bloom: BloomFilter<16384> = BloomFilter::with_hashes(3);
for i in 0..5000u64 {
bloom.insert(i);
}
let mut false_positives = 0;
for i in 5000..15000u64 {
if bloom.contains(i) {
false_positives += 1;
}
}
let fp_rate = false_positives as f64 / 10000.0;
assert!(
fp_rate < 0.20,
"False positive rate {} exceeds 20%",
fp_rate
);
}
#[test]
fn test_clear() {
let mut bloom: BloomFilter<16384> = BloomFilter::with_hashes(3);
bloom.insert(42);
assert!(bloom.contains(42));
bloom.clear();
assert!(!bloom.contains(42));
}
#[test]
fn test_memory_size() {
let bloom: BloomFilter<16384> = BloomFilter::with_hashes(3);
assert_eq!(bloom.memory_bytes(), 256 * 8);
}
#[test]
fn test_optimal_hash_count() {
let bloom: BloomFilter<16384> = BloomFilter::optimal(5000);
assert!(bloom.num_hashes() >= 1);
}
}