use crate::scalar::radical::{RadicalElement, RadicalLayer, RadicalTower};
use crate::scalar::rat::Rat;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub enum TrigError {
ZeroDenominator,
NonConstructible { num: i64, den: i64 },
}
impl std::fmt::Display for TrigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TrigError::ZeroDenominator => write!(f, "denominator is zero"),
TrigError::NonConstructible { num, den } => write!(
f,
"angle {}π/{} is not constructible with supported base angles",
num, den
),
}
}
}
impl std::error::Error for TrigError {}
fn gcd_u64(mut a: u64, mut b: u64) -> u64 {
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
pub fn exact_cos_sin_pi(num: i64, den: i64) -> Result<(RadicalElement, RadicalElement), TrigError> {
if den == 0 {
return Err(TrigError::ZeroDenominator);
}
let g = gcd_u64(num.unsigned_abs(), den.unsigned_abs()) as i64;
let (mut n, d) = if den > 0 {
(num / g, den / g)
} else {
(-num / g, -den / g)
};
n = ((n % (2 * d)) + 2 * d) % (2 * d);
let two_n = 2 * n;
if two_n < d {
base_cos_sin(n, d)
} else if two_n < 2 * d {
let (c, s) = base_cos_sin(d - n, d)?;
Ok((c.neg(), s))
} else if two_n < 3 * d {
let (c, s) = base_cos_sin(n - d, d)?;
Ok((c.neg(), s.neg()))
} else {
let (c, s) = base_cos_sin(2 * d - n, d)?;
Ok((c, s.neg()))
}
}
fn base_cos_sin(n: i64, d: i64) -> Result<(RadicalElement, RadicalElement), TrigError> {
let one = || RadicalElement::from_rat(Rat::ONE);
let zero = || RadicalElement::from_rat(Rat::ZERO);
let half = || RadicalElement::from_rat(Rat::new(1, 2));
if n == 0 {
return Ok((one(), zero()));
}
let g = gcd_u64(n.unsigned_abs(), d.unsigned_abs()) as i64;
let (rn, rd) = (n / g, d / g);
match (rn, rd) {
(1, 2) => Ok((zero(), one())),
(1, 6) => Ok((sqrt_over_2(3), half())),
(1, 4) => {
let v = sqrt_over_2(2);
Ok((v.clone(), v))
}
(1, 3) => Ok((half(), sqrt_over_2(3))),
(1, 12) => {
let (cos, sin) = pi_over_12_direct();
Ok((cos, sin))
}
(5, 12) => {
let (cos15, sin15) = pi_over_12_direct();
Ok((sin15, cos15))
}
_ => Err(TrigError::NonConstructible { num: rn, den: rd }),
}
}
fn pi_over_12_direct() -> (RadicalElement, RadicalElement) {
use crate::scalar::coeff::Coeff;
let layer_sqrt2 = RadicalLayer::sqrt(Rat::from(2));
let layer_sqrt3 = RadicalLayer::sqrt(Rat::from(3));
let tower = Arc::new(RadicalTower::two(layer_sqrt2, layer_sqrt3));
let q = Rat::new(1, 4);
let cos = RadicalElement::new(
vec![Coeff::ZERO, Coeff::ZERO, Coeff::Rat(q), Coeff::Rat(q)],
Arc::clone(&tower),
);
let sin = RadicalElement::new(
vec![Coeff::ZERO, Coeff::ZERO, Coeff::Rat(-q), Coeff::Rat(q)],
tower,
);
(cos, sin)
}
fn sqrt_over_2(d: i64) -> RadicalElement {
use crate::scalar::coeff::Coeff;
let layer = RadicalLayer::sqrt(Rat::from(d));
let tower = Arc::new(RadicalTower::single(layer));
RadicalElement::new(vec![Coeff::ZERO, Coeff::Rat(Rat::new(1, 2))], tower)
}
#[cfg(test)]
mod tests {
use super::*;
fn approx(r: &RadicalElement) -> f64 {
if let Some(rat) = r.to_rat() {
return rat.num() as f64 / rat.den() as f64;
}
let coeffs = &r.coeffs;
let tower = r.tower();
if tower.layers.len() == 1 {
let d = tower.layers[0].min_poly[0]; let sqrt_d = ((-d.num() as f64) / d.den() as f64).sqrt();
let c0 = coeffs[0].to_rat().unwrap_or(Rat::ZERO);
let c1 = coeffs[1].to_rat().unwrap_or(Rat::ZERO);
c0.num() as f64 / c0.den() as f64 + c1.num() as f64 / c1.den() as f64 * sqrt_d
} else {
let c0 = coeffs[0].to_rat().unwrap_or(Rat::ZERO);
c0.num() as f64 / c0.den() as f64
}
}
#[test]
fn cos_sin_zero() {
let (c, s) = exact_cos_sin_pi(0, 1).unwrap();
assert_eq!(c.to_rat(), Some(Rat::ONE));
assert_eq!(s.to_rat(), Some(Rat::ZERO));
}
#[test]
fn cos_sin_pi() {
let (c, s) = exact_cos_sin_pi(1, 1).unwrap();
assert_eq!(c.to_rat(), Some(Rat::from(-1)));
assert!(s.is_zero());
}
#[test]
fn cos_sin_pi_over_2() {
let (c, s) = exact_cos_sin_pi(1, 2).unwrap();
assert!(c.is_zero());
assert_eq!(s.to_rat(), Some(Rat::ONE));
}
#[test]
fn cos_sin_pi_over_3() {
let (c, s) = exact_cos_sin_pi(1, 3).unwrap();
assert_eq!(c.to_rat(), Some(Rat::new(1, 2)));
let s_approx = approx(&s);
assert!((s_approx - 0.866025).abs() < 0.001);
}
#[test]
fn cos_sin_pi_over_4() {
let (c, s) = exact_cos_sin_pi(1, 4).unwrap();
let c_approx = approx(&c);
let s_approx = approx(&s);
assert!((c_approx - std::f64::consts::FRAC_1_SQRT_2).abs() < 0.0001);
assert!((s_approx - std::f64::consts::FRAC_1_SQRT_2).abs() < 0.0001);
assert_eq!(c, s);
}
#[test]
fn cos_sin_pi_over_6() {
let (c, s) = exact_cos_sin_pi(1, 6).unwrap();
assert_eq!(s.to_rat(), Some(Rat::new(1, 2)));
let c_approx = approx(&c);
assert!((c_approx - 0.866025).abs() < 0.001);
}
#[test]
fn cos_sin_pi_over_12() {
let (c, s) = exact_cos_sin_pi(1, 12).unwrap();
let c2 = c.mul(&c);
let s2 = s.mul(&s);
let sum = c2.add(&s2);
assert_eq!(sum.to_rat(), Some(Rat::ONE));
}
#[test]
fn cos_sin_2pi_over_3() {
let (c, s) = exact_cos_sin_pi(2, 3).unwrap();
assert_eq!(c.to_rat(), Some(Rat::new(-1, 2)));
assert!(s.is_positive());
}
#[test]
fn cos_sin_3pi_over_2() {
let (c, s) = exact_cos_sin_pi(3, 2).unwrap();
assert!(c.is_zero());
assert_eq!(s.to_rat(), Some(Rat::from(-1)));
}
#[test]
fn cos_sin_negative_angle() {
let (c, s) = exact_cos_sin_pi(-1, 3).unwrap();
assert_eq!(c.to_rat(), Some(Rat::new(1, 2)));
assert!(s.is_negative());
}
#[test]
fn pythagorean_identity_all_base_angles() {
for (n, d) in [
(0, 1),
(1, 6),
(1, 4),
(1, 3),
(1, 2),
(2, 3),
(3, 4),
(5, 6),
(1, 1),
(1, 12),
(5, 12),
] {
let (c, s) = exact_cos_sin_pi(n, d).unwrap();
let sum = c.mul(&c).add(&s.mul(&s));
assert_eq!(
sum.to_rat(),
Some(Rat::ONE),
"cos²({}π/{}) + sin²({}π/{}) ≠ 1",
n,
d,
n,
d
);
}
}
#[test]
fn non_constructible_rejected() {
let err = exact_cos_sin_pi(1, 7).unwrap_err();
assert!(matches!(err, TrigError::NonConstructible { .. }));
}
#[test]
fn zero_denominator_rejected() {
let err = exact_cos_sin_pi(1, 0).unwrap_err();
assert!(matches!(err, TrigError::ZeroDenominator));
}
}