use crate::bounded::{NonNegative, UnitInterval};
use crate::error::{Error, Result};
use crate::kernel::circular_vectoring;
use crate::ops::algebraic::sqrt_nonneg;
use crate::tables::chebyshev::{COS_Q_HI, COS_Q_LO, SIN_P_HI, SIN_P_LO, horner};
use crate::traits::CordicNumber;
#[must_use]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn sin_cos<T: CordicNumber>(angle: T) -> (T, T) {
let pi = T::pi();
let frac_pi_2 = T::frac_pi_2();
let two_pi = pi + pi;
let reduced = if angle > pi || angle < -pi {
let quotient = angle.div(two_pi);
let n = quotient.round();
angle.saturating_sub(n.saturating_mul(two_pi))
} else {
angle
};
let reduced = if reduced > pi {
reduced.saturating_sub(two_pi)
} else if reduced < -pi {
reduced.saturating_add(two_pi)
} else {
reduced
};
let (reduced, negate) = if reduced > frac_pi_2 {
(reduced - pi, true)
} else if reduced < -frac_pi_2 {
(reduced + pi, true)
} else {
(reduced, false)
};
let one = T::one();
let frac_pi_4 = T::frac_pi_4();
let abs_reduced = reduced.abs();
let (poly_arg, swapped) = if abs_reduced >= frac_pi_4 {
(frac_pi_2.saturating_sub(abs_reduced), true)
} else {
(abs_reduced, false)
};
let u = poly_arg.saturating_mul(poly_arg);
let (sp_val, cp_val) = if T::frac_bits() >= 24 {
let sp = horner(&SIN_P_HI, u);
let sin_approx = poly_arg.saturating_add(poly_arg.saturating_mul(u).saturating_mul(sp));
let cp = horner(&COS_Q_HI, u);
(sin_approx, one.saturating_add(u.saturating_mul(cp)))
} else {
let sp = horner(&SIN_P_LO, u);
let sin_approx = poly_arg.saturating_add(poly_arg.saturating_mul(u).saturating_mul(sp));
let cp = horner(&COS_Q_LO, u);
(sin_approx, one.saturating_add(u.saturating_mul(cp)))
};
let (sin_unsigned, cos_val) = if swapped {
(cp_val, sp_val)
} else {
(sp_val, cp_val)
};
let sin_val = if reduced < T::zero() {
-sin_unsigned
} else {
sin_unsigned
};
if negate {
(-sin_val, -cos_val)
} else {
(sin_val, cos_val)
}
}
#[inline]
#[must_use]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn sin<T: CordicNumber>(angle: T) -> T {
sin_cos(angle).0
}
#[inline]
#[must_use]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn cos<T: CordicNumber>(angle: T) -> T {
sin_cos(angle).1
}
#[must_use]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn tan<T: CordicNumber>(angle: T) -> T {
let (s, c) = sin_cos(angle);
s.div(c)
}
#[must_use = "returns the arcsine result which should be handled"]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn asin<T: CordicNumber>(x: T) -> Result<T> {
let Some(unit_x) = UnitInterval::new(x) else {
return Err(Error::domain("asin", "value in range [-1, 1]"));
};
if x == T::one() {
return Ok(T::frac_pi_2());
}
if x == -T::one() {
return Ok(-T::frac_pi_2());
}
if x == T::zero() {
return Ok(T::zero());
}
let sqrt_term = sqrt_nonneg(NonNegative::one_minus_square(unit_x));
if sqrt_term < T::from_i1f63(0x0001_0000_0000_0000) {
return if x.is_positive() {
Ok(T::frac_pi_2())
} else {
Ok(-T::frac_pi_2())
};
}
Ok(atan(x.div(sqrt_term)))
}
#[must_use = "returns the arccosine result which should be handled"]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn acos<T: CordicNumber>(x: T) -> Result<T> {
asin(x).map(|a| T::frac_pi_2().saturating_sub(a))
}
#[must_use]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn atan<T: CordicNumber>(x: T) -> T {
let zero = T::zero();
let one = T::one();
if x == zero {
return zero;
}
let abs_x = x.abs();
if abs_x > one {
let recip = one.div(x);
let atan_recip = circular_vectoring(one, recip, zero).2;
if x.is_positive() {
T::frac_pi_2() - atan_recip
} else {
-T::frac_pi_2() - atan_recip
}
} else {
circular_vectoring(one, x, zero).2
}
}
#[must_use]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn atan2<T: CordicNumber>(y: T, x: T) -> T {
let zero = T::zero();
let pi = T::pi();
let frac_pi_2 = T::frac_pi_2();
if x == zero {
return if y.is_negative() {
-frac_pi_2
} else if y == zero {
zero } else {
frac_pi_2
};
}
if y == zero {
return if x.is_negative() { pi } else { zero };
}
let (_, _, base_angle) = circular_vectoring(x.abs(), y.abs(), zero);
match (x.is_negative(), y.is_negative()) {
(false, false) => base_angle,
(false, true) => -base_angle,
(true, false) => pi - base_angle,
(true, true) => base_angle - pi,
}
}