use core::str::FromStr;
use crate::{CBig, DBig, OxiNumError, OxiNumResult};
const GUARD: usize = 10;
fn make_dbig(s: &str) -> OxiNumResult<DBig> {
DBig::from_str(s).map_err(|e| OxiNumError::Parse(format!("{e}").into()))
}
#[inline]
fn mul_i(z: CBig) -> CBig {
CBig::from_parts(-z.im, z.re)
}
#[inline]
fn mul_neg_i(z: CBig) -> CBig {
CBig::from_parts(z.im, -z.re)
}
impl CBig {
pub fn asin(&self, precision: usize) -> OxiNumResult<CBig> {
if self.is_zero() {
return Ok(CBig::zero());
}
let guard = precision.saturating_add(GUARD);
let iz = mul_i(self.clone());
let z_sq = self * self;
let one_minus_z2 = &CBig::one() - &z_sq;
let sqrt_val = one_minus_z2.sqrt(guard)?;
let arg = &iz + &sqrt_val;
let ln_val = arg.ln(guard)?;
Ok(mul_neg_i(ln_val))
}
pub fn acos(&self, precision: usize) -> OxiNumResult<CBig> {
let guard = precision.saturating_add(GUARD);
let z_sq = self * self;
let one_minus_z2 = &CBig::one() - &z_sq;
let sqrt_val = one_minus_z2.sqrt(guard)?;
let i_sqrt = mul_i(sqrt_val);
let arg = self + &i_sqrt;
let ln_val = arg.ln(guard)?;
Ok(mul_neg_i(ln_val))
}
pub fn atan(&self, precision: usize) -> OxiNumResult<CBig> {
if self.is_zero() {
return Ok(CBig::zero());
}
let guard = precision.saturating_add(GUARD);
let half = make_dbig("0.5")?;
let iz = mul_i(self.clone());
let one = CBig::one();
let ln_minus = (&one - &iz).ln(guard)?;
let ln_plus = (&one + &iz).ln(guard)?;
let diff = &ln_minus - &ln_plus;
let i_diff = mul_i(diff);
let re = &i_diff.re * ½
let im = &i_diff.im * ½
Ok(CBig::from_parts(re, im))
}
pub fn asinh(&self, precision: usize) -> OxiNumResult<CBig> {
if self.is_zero() {
return Ok(CBig::zero());
}
let guard = precision.saturating_add(GUARD);
let z_sq_plus_one = &(self * self) + &CBig::one();
let sqrt_val = z_sq_plus_one.sqrt(guard)?;
let arg = self + &sqrt_val;
arg.ln(guard)
}
pub fn acosh(&self, precision: usize) -> OxiNumResult<CBig> {
let guard = precision.saturating_add(GUARD);
let one = CBig::one();
let sq1 = (self - &one).sqrt(guard)?;
let sq2 = (self + &one).sqrt(guard)?;
let product = &sq1 * &sq2;
let arg = self + &product;
arg.ln(guard)
}
pub fn atanh(&self, precision: usize) -> OxiNumResult<CBig> {
if self.is_zero() {
return Ok(CBig::zero());
}
let guard = precision.saturating_add(GUARD);
let half = make_dbig("0.5")?;
let one = CBig::one();
let ln_plus = (&one + self).ln(guard)?;
let ln_minus = (&one - self).ln(guard)?;
let diff = &ln_plus - &ln_minus;
let re = &diff.re * ½
let im = &diff.im * ½
Ok(CBig::from_parts(re, im))
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxinum_float::compute_pi;
const PREC: usize = 40;
const TOL: f64 = 1e-9;
fn c(re: f64, im: f64) -> CBig {
CBig::from_f64(re, im).expect("finite parts")
}
fn pi() -> DBig {
compute_pi(PREC + 10)
}
#[test]
fn asin_zero_is_zero() {
let r = CBig::zero().asin(PREC).expect("asin");
let (re, im) = r.to_f64_parts();
assert!(re.abs() < TOL, "re = {re}");
assert!(im.abs() < TOL, "im = {im}");
}
#[test]
fn atan_zero_is_zero() {
let r = CBig::zero().atan(PREC).expect("atan");
let (re, im) = r.to_f64_parts();
assert!(re.abs() < TOL, "re = {re}");
assert!(im.abs() < TOL, "im = {im}");
}
#[test]
fn asinh_zero_is_zero() {
let r = CBig::zero().asinh(PREC).expect("asinh");
let (re, im) = r.to_f64_parts();
assert!(re.abs() < TOL, "re = {re}");
assert!(im.abs() < TOL, "im = {im}");
}
#[test]
fn atanh_zero_is_zero() {
let r = CBig::zero().atanh(PREC).expect("atanh");
let (re, im) = r.to_f64_parts();
assert!(re.abs() < TOL, "re = {re}");
assert!(im.abs() < TOL, "im = {im}");
}
#[test]
fn asin_one_is_half_pi() {
let r = CBig::from_real(DBig::from(1u32)).asin(PREC).expect("asin");
let (re, im) = r.to_f64_parts();
let half_pi = pi().to_f64().value() / 2.0;
assert!(
(re - half_pi).abs() < TOL,
"re = {re}, expected π/2 ≈ {half_pi}"
);
assert!(im.abs() < TOL, "im = {im}");
}
#[test]
fn atan_one_is_quarter_pi() {
let r = CBig::from_real(DBig::from(1u32)).atan(PREC).expect("atan");
let (re, im) = r.to_f64_parts();
let quarter_pi = pi().to_f64().value() / 4.0;
assert!(
(re - quarter_pi).abs() < TOL,
"re = {re}, expected π/4 ≈ {quarter_pi}"
);
assert!(im.abs() < TOL, "im = {im}");
}
#[test]
fn acosh_one_is_zero() {
let r = CBig::from_real(DBig::from(1u32))
.acosh(PREC)
.expect("acosh");
let (re, im) = r.to_f64_parts();
assert!(re.abs() < TOL, "re = {re}");
assert!(im.abs() < TOL, "im = {im}");
}
#[test]
fn sin_asin_roundtrip() {
let z = c(0.3, 0.4);
let asin_z = z.asin(PREC).expect("asin");
let r = asin_z.sin(PREC).expect("sin");
let (re, im) = r.to_f64_parts();
assert!((re - 0.3).abs() < TOL, "re = {re}");
assert!((im - 0.4).abs() < TOL, "im = {im}");
}
#[test]
fn cos_acos_roundtrip() {
let z = c(0.3, 0.4);
let acos_z = z.acos(PREC).expect("acos");
let r = acos_z.cos(PREC).expect("cos");
let (re, im) = r.to_f64_parts();
assert!((re - 0.3).abs() < TOL, "re = {re}");
assert!((im - 0.4).abs() < TOL, "im = {im}");
}
#[test]
fn tanh_atanh_roundtrip() {
let z = c(0.2, 0.1);
let atanh_z = z.atanh(PREC).expect("atanh");
let r = atanh_z.tanh(PREC).expect("tanh");
let (re, im) = r.to_f64_parts();
assert!((re - 0.2).abs() < TOL, "re = {re}");
assert!((im - 0.1).abs() < TOL, "im = {im}");
}
#[test]
fn sinh_asinh_roundtrip() {
let z = c(0.3, 0.4);
let asinh_z = z.asinh(PREC).expect("asinh");
let r = asinh_z.sinh(PREC).expect("sinh");
let (re, im) = r.to_f64_parts();
assert!((re - 0.3).abs() < TOL, "re = {re}");
assert!((im - 0.4).abs() < TOL, "im = {im}");
}
}