use crate::{math::igamc, result::TestResult};
use std::f64::consts::LN_2;
pub fn approximate_entropy(bits: &[u8], m: usize) -> TestResult {
let n = bits.len();
if m >= 30 || (1usize << m) > n / 10 {
return TestResult::insufficient("nist::approximate_entropy", "m too large for n");
}
let phi_m = phi(bits, m, n);
let phi_m1 = phi(bits, m + 1, n);
let ap_en = phi_m - phi_m1;
let chi_sq = 2.0 * n as f64 * (LN_2 - ap_en);
let p_value = igamc(2.0_f64.powi(m as i32 - 1), chi_sq / 2.0);
TestResult::with_note(
"nist::approximate_entropy",
p_value,
format!("n={n}, m={m}, ApEn={ap_en:.6}, χ²={chi_sq:.4}"),
)
}
fn phi(bits: &[u8], m: usize, n: usize) -> f64 {
let table_size = 1usize << m;
let mut counts = vec![0u32; table_size];
for i in 0..n {
let mut pattern = 0usize;
for j in 0..m {
pattern = (pattern << 1) | bits[(i + j) % n] as usize;
}
counts[pattern] += 1;
}
let sum: f64 = counts
.iter()
.filter(|&&c| c > 0)
.map(|&c| {
let cf = c as f64;
cf * (cf / n as f64).ln()
})
.sum();
sum / n as f64
}