Skip to main content

p3_security/
error.rs

1//! Uniform currency for soundness error contributions.
2
3use alloc::vec::Vec;
4
5use libm::log2;
6use serde::Serialize;
7
8/// `-log2(error_probability)`. Higher = tighter bound.
9#[derive(Copy, Clone, Debug, PartialEq, Serialize)]
10pub struct ErrorBits(pub f64);
11
12impl ErrorBits {
13    pub const fn from_log2(bits: f64) -> Self {
14        Self(bits)
15    }
16
17    pub fn from_prob(p: f64) -> Self {
18        Self(-log2(p))
19    }
20
21    /// `-log2(sum of error probabilities)`. Tight composition; use this
22    /// when terms are independent or the union bound is the operative
23    /// bound.
24    pub fn sum(errors: &[Self]) -> Self {
25        let total: f64 = errors.iter().map(|e| pow2_neg(e.0)).sum();
26        Self::from_prob(total)
27    }
28
29    /// Minimum bits across terms. Loose but cheap — equivalent to taking
30    /// the largest single error probability.
31    #[allow(clippy::missing_const_for_fn)] // iterator / f64::min are not const
32    pub fn min(errors: &[Self]) -> Self {
33        let m = errors.iter().map(|e| e.0).fold(f64::INFINITY, f64::min);
34        Self(m)
35    }
36
37    pub const fn bits(self) -> f64 {
38        self.0
39    }
40
41    pub fn floor(self) -> usize {
42        libm::floor(self.0) as usize
43    }
44}
45
46fn pow2_neg(bits: f64) -> f64 {
47    libm::pow(2.0, -bits)
48}
49
50/// Convenience wrapper for `ErrorBits::sum` to keep call sites readable.
51pub fn sum_errors<I: IntoIterator<Item = ErrorBits>>(iter: I) -> ErrorBits {
52    let v: Vec<_> = iter.into_iter().collect();
53    ErrorBits::sum(&v)
54}