use crate::{math::erfc, result::TestResult};
use std::f64::consts::SQRT_2;
const WORD_LEN: usize = 5;
const ALPHA_SIZE: usize = 5;
const N_CATEGORIES5: usize = 3125; const N_CATEGORIES4: usize = 625; const N_SAMPLES: usize = 256_000;
const QDIFF_MEAN: f64 = 2500.0;
const QDIFF_STDDEV: f64 = 70.710_678;
pub fn count_ones_stream(words: &[u32]) -> TestResult {
let bytes_needed = N_SAMPLES + WORD_LEN - 1;
let words_needed = bytes_needed.div_ceil(4);
if words.len() < words_needed {
return TestResult::insufficient("diehard::count_ones_stream", "not enough words");
}
let letter_iter = words
.iter()
.flat_map(|&w| w.to_le_bytes())
.take(bytes_needed)
.map(hamming_letter);
count_ones_test(letter_iter, "diehard::count_ones_stream")
}
fn hamming_letter(b: u8) -> usize {
match b.count_ones() {
0..=2 => 0, 3 => 1, 4 => 2, 5 => 3, _ => 4, }
}
fn count_ones_test(mut letters: impl Iterator<Item = usize>, name: &'static str) -> TestResult {
let n = N_SAMPLES;
let lp = [
37.0_f64 / 256.0,
56.0 / 256.0,
70.0 / 256.0,
56.0 / 256.0,
37.0 / 256.0,
];
let nf = n as f64;
let mut counts5 = [0u32; N_CATEGORIES5];
let mut counts4 = [0u32; N_CATEGORIES4];
let mut word5 = 0usize;
for l in letters.by_ref().take(WORD_LEN - 1) {
word5 = word5 * ALPHA_SIZE + l;
}
for l in letters.take(n) {
word5 = (word5 * ALPHA_SIZE + l) % N_CATEGORIES5;
counts5[word5] += 1;
counts4[word5 / ALPHA_SIZE] += 1;
}
let q5: f64 = counts5
.iter()
.enumerate()
.map(|(w, &c)| {
let l = [w / 625, (w / 125) % 5, (w / 25) % 5, (w / 5) % 5, w % 5];
let exp = nf * lp[l[0]] * lp[l[1]] * lp[l[2]] * lp[l[3]] * lp[l[4]];
if exp < 5.0 {
return 0.0;
}
(c as f64 - exp).powi(2) / exp
})
.sum();
let q4: f64 = counts4
.iter()
.enumerate()
.map(|(w, &c)| {
let l = [w / 125, (w / 25) % 5, (w / 5) % 5, w % 5];
let exp = nf * lp[l[0]] * lp[l[1]] * lp[l[2]] * lp[l[3]];
if exp < 5.0 {
return 0.0;
}
(c as f64 - exp).powi(2) / exp
})
.sum();
let z = (q5 - q4 - QDIFF_MEAN) / QDIFF_STDDEV;
let p_value = erfc(z.abs() / SQRT_2);
TestResult::with_note(
name,
p_value,
format!(
"n={n}, Q5={q5:.2}, Q4={q4:.2}, Q5-Q4={:.2}, Z={z:.4}",
q5 - q4
),
)
}