use crate::{math::igamc, result::TestResult, rng::Rng};
use std::f64::consts::PI;
const N_PAIRS: usize = 100_000;
const KTBLSIZE: usize = 41;
#[rustfmt::skip]
const KPROB: [f64; KTBLSIZE] = [
0.0, 5.39e-09, 6.077e-08, 4.8421e-07, 2.94869e-06, 1.443266e-05,
5.908569e-05, 2.0658047e-04, 6.2764766e-04, 1.67993762e-03, 3.99620143e-03, 8.51629626e-03,
1.635214339e-02, 2.843154488e-02, 4.493723812e-02, 6.476525706e-02, 8.533638862e-02, 1.030000214e-01,
1.1407058851e-01, 1.1604146948e-01, 1.0853040184e-01, 9.336837411e-02, 7.389607162e-02, 5.380182248e-02,
3.601960159e-02, 2.215902902e-02, 1.251328472e-02, 6.47884418e-03, 3.06981507e-03, 1.32828179e-03,
5.2381841e-04, 1.8764452e-04, 6.084138e-05, 1.779885e-05, 4.66795e-06, 1.09504e-06,
2.2668e-07, 4.104e-08, 6.42e-09, 8.4e-10, 1.4e-10,
];
pub fn gcd_both(rng: &mut impl Rng) -> Vec<TestResult> {
let gnorm = 6.0 / (PI * PI);
let gtblsize = ((N_PAIRS as f64 * gnorm / 100.0).sqrt() as usize).max(3);
let mut gcd_counts = vec![0u32; gtblsize];
let mut step_counts = [0u32; KTBLSIZE];
let mut actual_pairs = 0usize;
for _ in 0..N_PAIRS {
let u = rng.next_u32();
let v = rng.next_u32();
if u == 0 || v == 0 {
continue;
}
actual_pairs += 1;
let (g, k) = euclid_gcd_with_steps(u, v);
let gcd_idx = (g as usize).min(gtblsize - 1);
gcd_counts[gcd_idx] += 1;
let step_idx = k.min(KTBLSIZE - 1);
step_counts[step_idx] += 1;
}
let n = actual_pairs as f64;
let gcd_expected: Vec<f64> = (0..gtblsize)
.map(|i| {
if i < 2 {
0.0
} else if i == gtblsize - 1 {
(i..=100_000)
.map(|j| n * gnorm / (j as f64 * j as f64))
.sum()
} else {
n * gnorm / (i as f64 * i as f64)
}
})
.collect();
let gcd_chi_sq: f64 = gcd_counts
.iter()
.zip(gcd_expected.iter())
.filter(|(_, &exp)| exp >= 5.0)
.map(|(&obs, &exp)| (obs as f64 - exp).powi(2) / exp)
.sum();
let gcd_df = gcd_counts
.iter()
.zip(gcd_expected.iter())
.filter(|(_, &exp)| exp >= 5.0)
.count()
.saturating_sub(1);
let p_gcd = igamc(gcd_df as f64 / 2.0, gcd_chi_sq / 2.0);
let step_chi_sq: f64 = step_counts
.iter()
.zip(KPROB.iter())
.filter(|(_, &p)| p * n >= 5.0)
.map(|(&obs, &p)| {
let exp = p * n;
(obs as f64 - exp).powi(2) / exp
})
.sum();
let step_df = step_counts
.iter()
.zip(KPROB.iter())
.filter(|(_, &p)| p * n >= 5.0)
.count()
.saturating_sub(1);
let p_steps = igamc(step_df as f64 / 2.0, step_chi_sq / 2.0);
vec![
TestResult::with_note(
"dieharder::gcd_distribution",
p_gcd,
format!("pairs={actual_pairs}, gtblsize={gtblsize}, χ²={gcd_chi_sq:.4}"),
),
TestResult::with_note(
"dieharder::gcd_step_counts",
p_steps,
format!("pairs={actual_pairs}, χ²={step_chi_sq:.4}"),
),
]
}
pub fn gcd(rng: &mut impl Rng) -> TestResult {
gcd_both(rng).remove(0)
}
fn euclid_gcd_with_steps(mut a: u32, mut b: u32) -> (u32, usize) {
let mut steps = 0usize;
while b != 0 {
let t = b;
b = a % b;
a = t;
steps += 1;
}
(a, steps)
}