bloomfilter-blocked-0.1.0.0: src/Data/BloomFilter/Classic/Calc.hs
-- | Various formulas for working with bloomfilters.
module Data.BloomFilter.Classic.Calc (
NumEntries,
BloomSize (..),
FPR,
sizeForFPR,
BitsPerEntry,
sizeForBits,
sizeForPolicy,
BloomPolicy (..),
policyFPR,
policyForFPR,
policyForBits,
) where
import Numeric
type FPR = Double
type BitsPerEntry = Double
type NumEntries = Int
-- | A policy on intended bloom filter size -- independent of the number of
-- elements.
--
-- We can decide a policy based on:
--
-- 1. a target false positive rate (FPR) using 'policyForFPR'
-- 2. a number of bits per entry using 'policyForBits'
--
-- A policy can be turned into a 'BloomSize' given a target 'NumEntries' using
-- 'sizeForPolicy'.
--
-- Either way we define the policy, we can inspect the result to see:
--
-- 1. The bits per entry 'policyBits'. This will determine the
-- size of the bloom filter in bits. In general the bits per entry can be
-- fractional. The final bloom filter size in will be rounded to a whole
-- number of bits.
-- 2. The number of hashes 'policyHashes'.
-- 3. The expected FPR for the policy using 'policyFPR'.
--
data BloomPolicy = BloomPolicy {
policyBits :: !Double,
policyHashes :: !Int
}
deriving stock Show
policyForFPR :: FPR -> BloomPolicy
policyForFPR fpr | fpr <= 0 || fpr >= 1 =
error "bloomPolicyForFPR: fpr out of range (0,1)"
policyForFPR fpr =
BloomPolicy {
policyBits = c,
policyHashes = k
}
where
-- There's a simper fomula to compute the number of bits, but it assumes
-- that k is a real. We must however round k to the nearest natural, and
-- so we have to use a more precise approximation, using the actual value
-- of k.
k :: Int; k' :: Double
k = max 1 (round ((-recip_log2) * log_fpr))
k' = fromIntegral k
c = negate k' / log1mexp (log_fpr / k')
log_fpr = log fpr
-- For the source of this formula, see
-- https://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives
--
-- We start with the FPR ε approximation that assumes independence for the
-- probabilities of each bit being set.
--
-- ε = (1 - e^(-kn/m))^k
--
-- And noting that bits per entry @c = m/n@, hence @-kn/m = -k/c@, hence
--
-- ε = (1-e^(-k/c))^k
--
-- And then we rearrange to get c, the number of bits per entry:
--
-- ε = (1-e^(-k/c))^k
-- ε = (1-exp (-k/c))^k
-- ε = exp (log (1 - exp (-k/c)) * k)
-- log ε = log (1 - exp (-k/c)) * k
-- log ε / k = log (1 - exp (-k/c))
-- exp (log ε / k) = 1 - exp (-k/c)
-- 1 - exp (log ε / k) = exp (-k/c)
-- log (1 - exp (log ε / k)) = -k/c
-- -k / log (1 - exp (log ε / k)) = c
-- -k / log1mexp (log ε / k) = c
policyForBits :: BitsPerEntry -> BloomPolicy
policyForBits c | c <= 0 =
error "policyForBits: bits per entry must be > 0"
policyForBits c =
BloomPolicy {
policyBits = c,
policyHashes = k
}
where
k = max 1 (round (c * log2))
-- For the source of this formula, see
-- https://en.wikipedia.org/wiki/Bloom_filter#Optimal_number_of_hash_functions
policyFPR :: BloomPolicy -> FPR
policyFPR BloomPolicy {
policyBits = c,
policyHashes = k
} =
negate (expm1 (negate (k' / c))) ** k'
where
k' = fromIntegral k
-- For the source of this formula, see
-- https://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives
--
-- We use the FPR ε approximation that assumes independence for the
-- probabilities of each bit being set.
--
-- ε = (1 - e^(-kn/m))^k
--
-- And noting that bits per entry @c = m/n@, hence @-kn/m = -k/c@, hence
--
-- ε = (1-e^(-k/c))^k
--
-- | Parameters for constructing a Bloom filter.
--
data BloomSize = BloomSize {
-- | The requested number of bits in the filter.
sizeBits :: !Int,
-- | The number of hash functions to use.
sizeHashes :: !Int
}
deriving stock Show
sizeForFPR :: FPR -> NumEntries -> BloomSize
sizeForFPR = sizeForPolicy . policyForFPR
sizeForBits :: BitsPerEntry -> NumEntries -> BloomSize
sizeForBits = sizeForPolicy . policyForBits
sizeForPolicy :: BloomPolicy -> NumEntries -> BloomSize
sizeForPolicy BloomPolicy {
policyBits = c,
policyHashes = k
} n =
BloomSize {
sizeBits = max 1 (ceiling (fromIntegral n * c)),
sizeHashes = max 1 k
}
log2, recip_log2 :: Double
log2 = log 2
recip_log2 = recip log2