use super::exp::exp_split;
use super::{scalbn_medium, Exp};
use crate::double::DenormDouble;
use crate::traits::{CastInto as _, Float, Int as _, Like};
pub(crate) trait SinhCosh<L = Like<Self>>: Exp {
fn expo2_hi_th() -> Self;
}
pub(crate) fn sinh<F: SinhCosh>(x: F) -> F {
let e = x.raw_exp();
if x >= F::expo2_hi_th() {
F::INFINITY
} else if x <= -F::expo2_hi_th() {
F::neg_infinity()
} else if e == F::MAX_RAW_EXP || e <= F::RawExp::ONE {
x
} else {
let (s, _) = sinh_cosh_inner(x);
s
}
}
pub(crate) fn cosh<F: SinhCosh>(x: F) -> F {
let e = x.raw_exp();
if x.abs() >= F::expo2_hi_th() {
F::INFINITY
} else if e == F::MAX_RAW_EXP {
x
} else if e <= F::RawExp::ONE {
F::one()
} else {
let (_, c) = sinh_cosh_inner(x);
c
}
}
pub(crate) fn sinh_cosh<F: SinhCosh>(x: F) -> (F, F) {
let e = x.raw_exp();
if x >= F::expo2_hi_th() {
(F::INFINITY, F::INFINITY)
} else if x <= -F::expo2_hi_th() {
(F::neg_infinity(), F::INFINITY)
} else if e == F::MAX_RAW_EXP {
(x, x)
} else if e <= F::RawExp::ONE {
(x, F::one())
} else {
let (s, c) = sinh_cosh_inner(x);
(s, c)
}
}
fn sinh_cosh_inner<F: Exp>(x: F) -> (F, F) {
let absx = x.abs();
let (k, r_hi, r_lo) = exp_split(absx);
let (r_hi, r_lo) = F::norm_hi_lo_full(r_hi, r_lo);
let (t1a, t1b) = sinh_cosh_inner_common_1(r_hi, r_lo);
if k > F::MANT_BITS.into() {
let t2 = scalbn_medium((r_hi + t1a) + F::one(), k - 1);
(t2.copysign(x), t2)
} else {
let (abss, c) = sinh_cosh_inner_common_2(k, r_hi, t1a, t1b);
(abss.to_single().copysign(x), c.to_single())
}
}
pub(super) fn sinh_cosh_inner_common_1<F: Exp>(r_hi: F, r_lo: F) -> (F, F) {
let three = F::one() + F::two();
let six = three * F::two();
let r2 = r_hi * r_hi;
let hr = F::half() * r_hi;
let hr2 = F::half() * r2;
let t1 = F::exp_m1_special_poly(r2);
let t2a = three - t1 * hr;
let t3a = hr2 * ((t1 - t2a) / (six - r_hi * t2a));
let t2b = three + t1 * hr;
let t3b = hr2 * ((t1 - t2b) / (six + r_hi * t2b));
let t4a = (r_hi * (r_lo - t3a) + r_lo) + hr2;
let t4b = (r_hi * (r_lo + t3b) - r_lo) + hr2;
(t4a, t4b)
}
pub(super) fn sinh_cosh_inner_common_2<F: Float>(
k: i32,
r: F,
t1a: F,
t1b: F,
) -> (DenormDouble<F>, DenormDouble<F>) {
let s1a = F::exp2i_fast((k - 1).cast_into());
let sra = r * s1a;
let st1a = t1a * s1a;
let s1b = F::exp2i_fast((-k - 1).cast_into());
let srb = r * s1b;
let st1b = t1b * s1b;
if k <= 1 {
let t2a = s1a - s1b;
let t3a = DenormDouble::new_qadd11(sra, srb).qradd1(t2a);
let abss = t3a.ladd(st1a - st1b);
let t2b = s1a + s1b;
let t3b = DenormDouble::new_qsub11(sra, srb).qradd1(t2b);
let c = t3b.ladd(st1a + st1b);
(abss, c)
} else {
let t2 = DenormDouble::new_qadd11(sra, st1a).qradd1(s1a);
let t3a = t2.qsub1(s1b);
let abss = t3a.ladd(srb - st1b);
let t3b = t2.qadd1(s1b);
let c = t3b.ladd(st1b - srb);
(abss, c)
}
}
#[cfg(test)]
mod tests {
use crate::traits::Float;
use crate::FloatMath;
fn test<F: Float + FloatMath>(hi_th: &str) {
use crate::{cosh, sinh, sinh_cosh};
let test_nan = |arg: F| {
let sinh1 = sinh(arg);
let cosh1 = cosh(arg);
let (sinh2, cosh2) = sinh_cosh(arg);
assert_is_nan!(sinh1);
assert_is_nan!(cosh1);
assert_is_nan!(sinh2);
assert_is_nan!(cosh2);
};
let test_value = |arg: F, expected_sinh: F, expected_cosh: F| {
let sinh1 = sinh(arg);
let cosh1 = cosh(arg);
let (sinh2, cosh2) = sinh_cosh(arg);
assert_total_eq!(sinh1, expected_sinh);
assert_total_eq!(cosh1, expected_cosh);
assert_total_eq!(sinh2, expected_sinh);
assert_total_eq!(cosh2, expected_cosh);
};
let hi_th = F::parse(hi_th);
test_nan(F::NAN);
test_value(F::INFINITY, F::INFINITY, F::INFINITY);
test_value(F::neg_infinity(), F::neg_infinity(), F::INFINITY);
test_value(hi_th, F::INFINITY, F::INFINITY);
test_value(-hi_th, F::neg_infinity(), F::INFINITY);
test_value(hi_th + F::half(), F::INFINITY, F::INFINITY);
test_value(-(hi_th + F::half()), F::neg_infinity(), F::INFINITY);
test_value(hi_th + F::one(), F::INFINITY, F::INFINITY);
test_value(-(hi_th + F::one()), F::neg_infinity(), F::INFINITY);
test_value(F::ZERO, F::ZERO, F::one());
test_value(-F::ZERO, -F::ZERO, F::one());
}
#[test]
fn test_f32() {
test::<f32>("89.5");
}
#[cfg(feature = "soft-float")]
#[test]
fn test_soft_f32() {
test::<crate::SoftF32>("89.5");
}
#[test]
fn test_f64() {
test::<f64>("710.5");
}
#[cfg(feature = "soft-float")]
#[test]
fn test_soft_f64() {
test::<crate::SoftF64>("710.5");
}
}