#![cfg(all(feature = "complex", feature = "float"))]
use puremp::{Complex, Float, RoundingMode};
const P: u64 = 200;
const M: RoundingMode = RoundingMode::Nearest;
fn f(v: f64) -> Float {
Float::from_f64(v, P, M)
}
fn cx(re: f64, im: f64) -> Complex<Float> {
Complex {
re: f(re),
im: f(im),
}
}
fn close(a: &Float, b: f64) -> bool {
(a.to_f64() - b).abs() < 1e-12 * (1.0 + b.abs())
}
fn cclose(z: &Complex<Float>, re: f64, im: f64) -> bool {
close(&z.re, re) && close(&z.im, im)
}
fn ceq(a: &Complex<Float>, b: &Complex<Float>) -> bool {
cclose(a, b.re.to_f64(), b.im.to_f64())
}
fn one() -> Complex<Float> {
cx(1.0, 0.0)
}
#[test]
fn tan_is_sin_over_cos() {
let z = cx(0.7, 0.3);
assert!(ceq(&z.tan(), &z.sin().div(&z.cos())));
assert!(ceq(&z.cot(), &z.cos().div(&z.sin())));
let prod = z.tan().mul(&z.cot());
assert!(cclose(&prod, 1.0, 0.0));
let r = cx(0.4, 0.0).tan();
assert!(close(&r.re, 0.4f64.tan()));
assert!(close(&r.im, 0.0));
}
#[test]
fn hyperbolic_identities() {
let z = cx(0.6, 0.8);
let (s, c) = (z.sinh(), z.cosh());
let d = c.mul(&c).sub(&s.mul(&s));
assert!(cclose(&d, 1.0, 0.0), "{:?}", (d.re.to_f64(), d.im.to_f64()));
assert!(ceq(&z.tanh(), &s.div(&c)));
let t = cx(0.9, 0.0).tanh();
assert!(close(&t.re, 0.9f64.tanh()));
assert!(close(&t.im, 0.0));
let y = 0.5;
assert!(cclose(&cx(0.0, y).sinh(), 0.0, y.sin()));
assert!(cclose(&cx(0.0, y).cosh(), y.cos(), 0.0));
}
#[test]
fn inverse_trig_round_trips() {
let z = cx(0.3, 0.4);
assert!(ceq(&z.asin().sin(), &z));
assert!(ceq(&z.acos().cos(), &z));
assert!(ceq(&z.atan().tan(), &z));
}
#[test]
fn inverse_hyperbolic_round_trips() {
let z = cx(0.3, 0.4);
assert!(ceq(&z.asinh().sinh(), &z));
assert!(ceq(&z.atanh().tanh(), &z));
let w = cx(1.7, 0.5);
assert!(ceq(&w.acosh().cosh(), &w));
}
#[test]
fn known_values() {
assert!(close(&one().atan().re, core::f64::consts::FRAC_PI_4));
assert!(close(&one().atan().im, 0.0));
let asin2 = cx(2.0, 0.0).asin();
let acosh2 = (2.0 + 3f64.sqrt()).ln();
assert!(cclose(&asin2, core::f64::consts::FRAC_PI_2, -acosh2));
let ac = cx(2.0, 0.0).acosh();
assert!(cclose(&ac, acosh2, 0.0));
let at = cx(0.5, 0.0).atanh();
assert!(close(&at.re, 0.5 * 3f64.ln()));
assert!(close(&at.im, 0.0));
let ai = cx(0.0, 0.5).atan();
assert!(cclose(&ai, 0.0, 0.5 * 3f64.ln()));
}
#[test]
fn asin_plus_acos_is_half_pi() {
let z = cx(0.35, -0.2);
let s = z.asin().add(&z.acos());
assert!(cclose(&s, core::f64::consts::FRAC_PI_2, 0.0));
}
#[test]
fn atan_log_identity() {
let z = cx(0.4, 0.25);
let ipz = Complex {
re: z.re.clone(),
im: Float::add(&z.im, &f(1.0), P, M),
}; let imz = Complex {
re: Float::neg(&z.re),
im: Float::sub(&f(1.0), &z.im, P, M),
}; let diff = ipz.ln().sub(&imz.ln());
let expect = Complex {
re: Float::div(&Float::neg(&diff.im), &f(2.0), P, M),
im: Float::div(&diff.re, &f(2.0), P, M),
};
assert!(ceq(&z.atan(), &expect));
}