use num::rational::Ratio;
#[must_use]
pub fn dedekind_sum(h: i128, k: i128) -> Ratio<i128> {
assert!(k > 0, "Dedekind sum s(h,k) requires k >= 1, got k = {k}");
let half = Ratio::new(1, 2);
let sawtooth_over_k = |numerator_mod_k: i128| {
if numerator_mod_k == 0 {
Ratio::new(0, 1)
} else {
Ratio::new(numerator_mod_k, k) - half
}
};
(1..k).fold(Ratio::new(0, 1), |acc, r| {
let first = sawtooth_over_k(r);
let second = sawtooth_over_k((h * r).rem_euclid(k));
acc + first * second
})
}
#[cfg(test)]
mod tests {
use super::dedekind_sum;
use num::rational::Ratio;
#[test]
fn empty_sum_at_k_one() {
assert_eq!(dedekind_sum(0, 1), Ratio::new(0, 1));
assert_eq!(dedekind_sum(5, 1), Ratio::new(0, 1));
}
#[test]
fn s_one_two_vanishes() {
assert_eq!(dedekind_sum(1, 2), Ratio::new(0, 1));
}
#[test]
fn s_one_k_matches_closed_form() {
for k in 2..20 {
let expected = Ratio::new((k - 1) * (k - 2), 12 * k);
assert_eq!(dedekind_sum(1, k), expected, "k = {k}");
}
}
#[test]
fn reciprocity_law() {
for (h, k) in [(2, 3), (3, 5), (4, 7), (5, 9), (7, 12)] {
let lhs = dedekind_sum(h, k) + dedekind_sum(k, h);
let rhs = Ratio::new(-1, 4)
+ (Ratio::new(h, k) + Ratio::new(k, h) + Ratio::new(1, h * k)) / 12;
assert_eq!(lhs, rhs, "h = {h}, k = {k}");
}
}
}