use crate::{
error::{assert_limited_precision, FpError},
fbig::FBig,
math::{
cache::{reborrow_cache, ConstCache},
FpResult,
},
repr::{Context, Repr, Word},
round::Round,
};
use dashu_base::{Abs, AbsOrd, Approximation::Exact, Sign};
impl<R: Round> Context<R> {
pub fn sinh<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if x.is_infinite() {
return Ok(Exact(FBig::new(Repr::infinity_with_sign(x.sign()), *self)));
}
assert_limited_precision(self.precision);
if x.significand.is_zero() {
return Ok(Exact(FBig::new(signed_zero_repr(x), *self)));
}
let work = Context::<R>::new(self.precision + 50);
let x_f = FBig::<R, B>::new(work.repr_round_ref(x).value(), work);
let neg_x = -x_f.clone();
let ep = work.exp_m1(&x_f.repr, reborrow_cache(&mut cache));
let em = work.exp_m1(&neg_x.repr, reborrow_cache(&mut cache));
match (ep, em) {
(Ok(ep), Ok(em)) => {
Ok(((ep.value() - em.value()) / 2i32).with_precision(self.precision))
}
_ => Err(FpError::Overflow(x.sign())),
}
}
pub fn cosh<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if x.is_infinite() {
return Ok(Exact(FBig::new(Repr::infinity(), *self)));
}
assert_limited_precision(self.precision);
if x.significand.is_zero() {
return Ok(Exact(FBig::new(Repr::one(), *self)));
}
let work = Context::<R>::new(self.precision + 50);
let x_f = FBig::<R, B>::new(work.repr_round_ref(x).value(), work);
let neg_x = -x_f.clone();
let ep = work.exp_m1(&x_f.repr, reborrow_cache(&mut cache));
let em = work.exp_m1(&neg_x.repr, reborrow_cache(&mut cache));
match (ep, em) {
(Ok(ep), Ok(em)) => Ok(((ep.value() + em.value()) / 2i32 + FBig::<R, B>::ONE)
.with_precision(self.precision)),
_ => Err(FpError::Overflow(Sign::Positive)),
}
}
pub fn sinh_cosh<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> (FpResult<FBig<R, B>>, FpResult<FBig<R, B>>) {
if x.is_infinite() {
return (
Ok(Exact(FBig::new(Repr::infinity_with_sign(x.sign()), *self))),
Ok(Exact(FBig::new(Repr::infinity(), *self))),
);
}
assert_limited_precision(self.precision);
if x.significand.is_zero() {
return (
Ok(Exact(FBig::new(signed_zero_repr(x), *self))),
Ok(Exact(FBig::new(Repr::one(), *self))),
);
}
let work = Context::<R>::new(self.precision + 50);
let x_f = FBig::<R, B>::new(work.repr_round_ref(x).value(), work);
let neg_x = -x_f.clone();
let ep = work.exp_m1(&x_f.repr, reborrow_cache(&mut cache));
let em = work.exp_m1(&neg_x.repr, reborrow_cache(&mut cache));
match (ep, em) {
(Ok(ep), Ok(em)) => {
let ep = ep.value();
let em = em.value();
let sinh_val = ((ep.clone() - em.clone()) / 2i32).with_precision(self.precision);
let cosh_val =
((ep + em) / 2i32 + FBig::<R, B>::ONE).with_precision(self.precision);
(Ok(sinh_val), Ok(cosh_val))
}
_ => (Err(FpError::Overflow(x.sign())), Err(FpError::Overflow(Sign::Positive))),
}
}
pub fn tanh<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if x.is_infinite() {
let one = FBig::new(Repr::one(), *self);
return Ok(Exact(if x.sign() == Sign::Negative {
-one
} else {
one
}));
}
assert_limited_precision(self.precision);
if x.significand.is_zero() {
return Ok(Exact(FBig::new(signed_zero_repr(x), *self)));
}
let work = Context::<R>::new(self.precision + 50);
let x_f = FBig::<R, B>::new(work.repr_round_ref(x).value(), work);
let two_x = x_f.clone() * 2i32;
match work.exp_m1(&two_x.repr, reborrow_cache(&mut cache)) {
Err(FpError::Overflow(_)) => Ok(FBig::ONE.with_precision(self.precision)),
Ok(e) => {
let e = e.value();
Ok((e.clone() / (e.clone() + 2i32)).with_precision(self.precision))
}
Err(other) => Err(other),
}
}
pub fn asinh<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if x.is_infinite() {
return Ok(Exact(FBig::new(Repr::infinity_with_sign(x.sign()), *self)));
}
assert_limited_precision(self.precision);
if x.significand.is_zero() {
return Ok(Exact(FBig::new(signed_zero_repr(x), *self)));
}
let work = Context::<R>::new(self.precision + 50);
let x_f = FBig::<R, B>::new(work.repr_round_ref(x).value(), work);
let sign = x_f.sign();
let abs_x = x_f.abs();
let arg = match work.sqr(&abs_x.repr) {
Ok(x_sq) => {
let x_sq = x_sq.value();
let sqrt_plus_one = work.sqrt(&(x_sq.clone() + FBig::<R, B>::ONE).repr)?.value()
+ FBig::<R, B>::ONE;
abs_x.clone() + x_sq / sqrt_plus_one
}
Err(FpError::Overflow(_)) => {
let ln_val = work
.ln(&(abs_x.clone() * 2i32).repr, reborrow_cache(&mut cache))?
.value();
return Ok(apply_sign(ln_val, sign).with_precision(self.precision));
}
Err(other) => return Err(other),
};
let res = work.ln_1p(&arg.repr, reborrow_cache(&mut cache))?.value();
Ok(apply_sign(res, sign).with_precision(self.precision))
}
pub fn acosh<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if x.is_infinite() {
if x.sign() == Sign::Negative {
return Err(FpError::OutOfDomain);
}
return Ok(Exact(FBig::new(Repr::infinity(), *self)));
}
assert_limited_precision(self.precision);
if x.sign() == Sign::Negative
|| FBig::<R, B>::new(x.clone(), *self)
.abs_cmp(&FBig::ONE)
.is_lt()
{
return Err(FpError::OutOfDomain);
}
if x.is_one() {
return Ok(Exact(FBig::new(Repr::zero(), *self)));
}
let work = Context::<R>::new(self.precision + 50);
let x_f = FBig::<R, B>::new(work.repr_round_ref(x).value(), work);
let xm1 = &x_f - FBig::<R, B>::ONE;
let xp1 = &x_f + FBig::<R, B>::ONE;
let arg = match work.mul(&xm1.repr, &xp1.repr) {
Ok(prod) => xm1.clone() + work.sqrt(&prod.value().repr)?.value(),
Err(FpError::Overflow(_)) => {
let ln_val = work
.ln(&(x_f.clone() * 2i32).repr, reborrow_cache(&mut cache))?
.value();
return Ok(ln_val.with_precision(self.precision));
}
Err(other) => return Err(other),
};
let res = work.ln_1p(&arg.repr, reborrow_cache(&mut cache))?.value();
Ok(res.with_precision(self.precision))
}
pub fn atanh<const B: Word>(
&self,
x: &Repr<B>,
mut cache: Option<&mut ConstCache>,
) -> FpResult<FBig<R, B>> {
if x.is_infinite() {
return Err(FpError::OutOfDomain);
}
assert_limited_precision(self.precision);
if x.significand.is_zero() {
return Ok(Exact(FBig::new(signed_zero_repr(x), *self)));
}
match FBig::<R, B>::new(x.clone(), *self).abs_cmp(&FBig::ONE) {
core::cmp::Ordering::Greater => return Err(FpError::OutOfDomain),
core::cmp::Ordering::Equal => {
return Ok(Exact(FBig::new(Repr::infinity_with_sign(x.sign()), *self)));
}
_ => {}
}
let work = Context::<R>::new(self.precision + 50);
let x_f = FBig::<R, B>::new(work.repr_round_ref(x).value(), work);
let ratio = (x_f.clone() * 2i32) / (FBig::<R, B>::ONE - &x_f);
let res = work.ln_1p(&ratio.repr, reborrow_cache(&mut cache))?.value();
Ok((res / 2i32).with_precision(self.precision))
}
}
impl<R: Round, const B: Word> FBig<R, B> {
#[inline]
pub fn sinh(&self) -> Self {
self.context.unwrap_fp(self.context.sinh(&self.repr, None))
}
#[inline]
pub fn cosh(&self) -> Self {
self.context.unwrap_fp(self.context.cosh(&self.repr, None))
}
#[inline]
pub fn sinh_cosh(&self) -> (Self, Self) {
let (s, c) = self.context.sinh_cosh(&self.repr, None);
(self.context.unwrap_fp(s), self.context.unwrap_fp(c))
}
#[inline]
pub fn tanh(&self) -> Self {
self.context.unwrap_fp(self.context.tanh(&self.repr, None))
}
#[inline]
pub fn asinh(&self) -> Self {
self.context.unwrap_fp(self.context.asinh(&self.repr, None))
}
#[inline]
pub fn acosh(&self) -> Self {
self.context.unwrap_fp(self.context.acosh(&self.repr, None))
}
#[inline]
pub fn atanh(&self) -> Self {
self.context.unwrap_fp(self.context.atanh(&self.repr, None))
}
}
fn signed_zero_repr<const B: Word>(x: &Repr<B>) -> Repr<B> {
if x.is_neg_zero() {
Repr::neg_zero()
} else {
Repr::zero()
}
}
fn apply_sign<R: Round, const B: Word>(v: FBig<R, B>, sign: Sign) -> FBig<R, B> {
if sign == Sign::Negative {
-v
} else {
v
}
}