use crate::bounded::{AtLeastOne, NonNegative, OpenUnitInterval};
use crate::error::{Error, Result};
use crate::kernel::hyperbolic_vectoring;
use crate::ops::algebraic::sqrt_nonneg;
use crate::traits::CordicNumber;
const HYPERBOLIC_CONVERGENCE_LIMIT_FRAC_I1F63: i64 = 0x0F22_3D70_A3D7_0A3D;
const ATANH_REDUCTION_THRESHOLD_I1F63: i64 = 0x6000_0000_0000_0000;
#[must_use]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn sinh_cosh<T: CordicNumber>(x: T) -> (T, T) {
let one = T::one();
let limit = one.saturating_add(T::from_i1f63(HYPERBOLIC_CONVERGENCE_LIMIT_FRAC_I1F63));
let mut reduced = x;
let mut depth: u32 = 0;
while reduced.abs() > limit && depth < T::total_bits() {
reduced = reduced >> 1;
depth += 1;
}
let u = reduced.saturating_mul(reduced);
let mut sp = one;
let (mut sh, mut ch) = if T::frac_bits() >= 24 && T::total_bits() >= T::frac_bits() + 9 {
sp = one.saturating_add(u.div(T::from_num(156)).saturating_mul(sp));
sp = one.saturating_add(u.div(T::from_num(110)).saturating_mul(sp));
sp = one.saturating_add(u.div(T::from_num(72)).saturating_mul(sp));
sp = one.saturating_add(u.div(T::from_num(42)).saturating_mul(sp));
sp = one.saturating_add(u.div(T::from_num(20)).saturating_mul(sp));
sp = one.saturating_add(u.div(T::from_num(6)).saturating_mul(sp));
let sinh_approx = reduced.saturating_mul(sp);
let mut cp = one;
cp = one.saturating_add(u.div(T::from_num(182)).saturating_mul(cp));
cp = one.saturating_add(u.div(T::from_num(132)).saturating_mul(cp));
cp = one.saturating_add(u.div(T::from_num(90)).saturating_mul(cp));
cp = one.saturating_add(u.div(T::from_num(56)).saturating_mul(cp));
cp = one.saturating_add(u.div(T::from_num(30)).saturating_mul(cp));
cp = one.saturating_add(u.div(T::from_num(12)).saturating_mul(cp));
cp = one.saturating_add(u.div(T::from_num(2)).saturating_mul(cp));
(sinh_approx, cp)
} else {
sp = one;
sp = one.saturating_add(u.div(T::from_num(72)).saturating_mul(sp));
sp = one.saturating_add(u.div(T::from_num(42)).saturating_mul(sp));
sp = one.saturating_add(u.div(T::from_num(20)).saturating_mul(sp));
sp = one.saturating_add(u.div(T::from_num(6)).saturating_mul(sp));
let sinh_approx = reduced.saturating_mul(sp);
let mut cp = one;
cp = one.saturating_add(u.div(T::from_num(90)).saturating_mul(cp));
cp = one.saturating_add(u.div(T::from_num(56)).saturating_mul(cp));
cp = one.saturating_add(u.div(T::from_num(30)).saturating_mul(cp));
cp = one.saturating_add(u.div(T::from_num(12)).saturating_mul(cp));
cp = one.saturating_add(u.div(T::from_num(2)).saturating_mul(cp));
(sinh_approx, cp)
};
for _ in 0..depth {
let new_sh = sh.saturating_mul(ch).saturating_mul(T::two());
let new_ch = ch.saturating_mul(ch).saturating_add(sh.saturating_mul(sh));
sh = new_sh;
ch = new_ch;
}
(sh, ch)
}
#[inline]
#[must_use]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn sinh<T: CordicNumber>(x: T) -> T {
sinh_cosh(x).0
}
#[inline]
#[must_use]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn cosh<T: CordicNumber>(x: T) -> T {
sinh_cosh(x).1
}
#[must_use]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn tanh<T: CordicNumber>(x: T) -> T {
let (s, c) = sinh_cosh(x);
s.div(c)
}
#[must_use = "returns the hyperbolic cotangent result which should be handled"]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn coth<T: CordicNumber>(x: T) -> Result<T> {
if x == T::zero() {
return Err(Error::domain("coth", "non-zero value"));
}
let (s, c) = sinh_cosh(x);
Ok(c.div(s))
}
#[must_use]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn asinh<T: CordicNumber>(x: T) -> T {
if x == T::zero() {
return T::zero();
}
let sqrt_term = sqrt_nonneg(NonNegative::one_plus_square(x));
let arg = OpenUnitInterval::from_div_by_sqrt_one_plus_square(x, sqrt_term);
atanh_open(arg)
}
#[must_use = "returns the inverse hyperbolic cosine result which should be handled"]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn acosh<T: CordicNumber>(x: T) -> Result<T> {
let at_least_one = AtLeastOne::new(x).ok_or_else(|| Error::domain("acosh", "value >= 1"))?;
if x == T::one() {
return Ok(T::zero());
}
let sqrt_term = sqrt_nonneg(NonNegative::square_minus_one(at_least_one));
let arg = OpenUnitInterval::from_sqrt_square_minus_one_div(sqrt_term, at_least_one);
Ok(atanh_open(arg))
}
#[must_use = "returns the inverse hyperbolic tangent result which should be handled"]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn atanh<T: CordicNumber>(x: T) -> Result<T> {
OpenUnitInterval::new(x)
.map(atanh_open)
.ok_or_else(|| Error::domain("atanh", "value in range (-1, 1)"))
}
#[must_use]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn atanh_open<T: CordicNumber>(x: OpenUnitInterval<T>) -> T {
atanh_core(x.get())
}
fn atanh_core<T: CordicNumber>(x: T) -> T {
let zero = T::zero();
let one = T::one();
if x == zero {
return zero;
}
let threshold = T::from_i1f63(ATANH_REDUCTION_THRESHOLD_I1F63);
if x.abs() <= threshold {
let (_, _, z) = hyperbolic_vectoring(one, x, zero);
return z;
}
let half = T::half();
let atanh_half = T::from_i1f63(crate::tables::hyperbolic::ATANH_HALF);
let sign = if x.is_negative() { -one } else { one };
let mut abs_x = x.abs();
let mut accumulated = zero;
let mut i: u32 = 0;
while abs_x > threshold && i < T::frac_bits() {
let numerator = abs_x.saturating_sub(half);
let denominator = one.saturating_sub(half.saturating_mul(abs_x));
abs_x = numerator.div(denominator);
accumulated = accumulated.saturating_add(atanh_half);
i += 1;
}
let (_, _, z) = hyperbolic_vectoring(one, abs_x, zero);
sign.saturating_mul(accumulated.saturating_add(z))
}
#[must_use = "returns the inverse hyperbolic cotangent result which should be handled"]
#[cfg_attr(feature = "verify-no-panic", no_panic::no_panic)]
pub fn acoth<T: CordicNumber>(x: T) -> Result<T> {
let one = T::one();
if x.abs() <= one {
return Err(Error::domain("acoth", "|value| > 1"));
}
let recip = one.div(x);
Ok(atanh_core(recip))
}