1use oxinum_core::OxiNumResult;
32use oxinum_float::native::{BigFloat, RoundingMode};
33
34use super::BigComplex;
35
36const GUARD: u32 = 10;
38
39fn bf_sinh(x: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
45 let ex = x.exp(prec, mode)?;
46 let e_neg = (-x).exp(prec, mode)?;
47 let half = BigFloat::from_f64(0.5, prec)?;
48 let diff = &ex - &e_neg;
49 Ok((&diff * &half).with_precision(prec, mode))
50}
51
52fn bf_cosh(x: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
54 let ex = x.exp(prec, mode)?;
55 let e_neg = (-x).exp(prec, mode)?;
56 let half = BigFloat::from_f64(0.5, prec)?;
57 let sum = &ex + &e_neg;
58 Ok((&sum * &half).with_precision(prec, mode))
59}
60
61fn bf_tanh(x: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
68 let sinh = bf_sinh(x, prec, mode)?;
69 let cosh = bf_cosh(x, prec, mode)?;
70 Ok(sinh
71 .div_ref_with_mode(&cosh, mode)?
72 .with_precision(prec, mode))
73}
74
75impl BigComplex {
76 pub fn sin(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
92 let guard = prec.saturating_add(GUARD);
93 let a = self.re.clone().with_precision(guard, mode);
94 let b = self.im.clone().with_precision(guard, mode);
95
96 let sin_a = a.sin(guard, mode)?;
97 let cos_a = a.cos(guard, mode)?;
98 let cosh_b = bf_cosh(&b, guard, mode)?;
99 let sinh_b = bf_sinh(&b, guard, mode)?;
100
101 let re = (&sin_a * &cosh_b).with_precision(prec, mode);
102 let im = (&cos_a * &sinh_b).with_precision(prec, mode);
103 Ok(BigComplex { re, im })
104 }
105
106 pub fn cos(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
122 let guard = prec.saturating_add(GUARD);
123 let a = self.re.clone().with_precision(guard, mode);
124 let b = self.im.clone().with_precision(guard, mode);
125
126 let cos_a = a.cos(guard, mode)?;
127 let sin_a = a.sin(guard, mode)?;
128 let cosh_b = bf_cosh(&b, guard, mode)?;
129 let sinh_b = bf_sinh(&b, guard, mode)?;
130
131 let re = (&cos_a * &cosh_b).with_precision(prec, mode);
132 let prod = &sin_a * &sinh_b;
134 let im = (-&prod).with_precision(prec, mode);
135 Ok(BigComplex { re, im })
136 }
137
138 pub fn sinh(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
145 let guard = prec.saturating_add(GUARD);
146 let a = self.re.clone().with_precision(guard, mode);
147 let b = self.im.clone().with_precision(guard, mode);
148
149 let sinh_a = bf_sinh(&a, guard, mode)?;
150 let cosh_a = bf_cosh(&a, guard, mode)?;
151 let cos_b = b.cos(guard, mode)?;
152 let sin_b = b.sin(guard, mode)?;
153
154 let re = (&sinh_a * &cos_b).with_precision(prec, mode);
155 let im = (&cosh_a * &sin_b).with_precision(prec, mode);
156 Ok(BigComplex { re, im })
157 }
158
159 pub fn cosh(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
175 let guard = prec.saturating_add(GUARD);
176 let a = self.re.clone().with_precision(guard, mode);
177 let b = self.im.clone().with_precision(guard, mode);
178
179 let cosh_a = bf_cosh(&a, guard, mode)?;
180 let sinh_a = bf_sinh(&a, guard, mode)?;
181 let cos_b = b.cos(guard, mode)?;
182 let sin_b = b.sin(guard, mode)?;
183
184 let re = (&cosh_a * &cos_b).with_precision(prec, mode);
185 let im = (&sinh_a * &sin_b).with_precision(prec, mode);
186 Ok(BigComplex { re, im })
187 }
188
189 pub fn tan(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
197 let guard = prec.saturating_add(GUARD);
198 let sin_z = self.sin(guard, mode)?;
199 let cos_z = self.cos(guard, mode)?;
200 sin_z.checked_div(&cos_z, prec, mode)
201 }
202
203 pub fn tanh(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
211 let guard = prec.saturating_add(GUARD);
212
213 if self.im.is_zero() {
217 let a = self.re.clone().with_precision(guard, mode);
218 let t = bf_tanh(&a, guard, mode)?.with_precision(prec, mode);
219 return Ok(BigComplex {
220 re: t,
221 im: BigFloat::zero(prec),
222 });
223 }
224
225 let sinh_z = self.sinh(guard, mode)?;
226 let cosh_z = self.cosh(guard, mode)?;
227 sinh_z.checked_div(&cosh_z, prec, mode)
228 }
229}
230
231#[cfg(test)]
236mod tests {
237 use super::*;
238
239 const PREC: u32 = 80;
240 const MODE: RoundingMode = RoundingMode::HalfEven;
241
242 fn c(re: f64, im: f64) -> BigComplex {
243 BigComplex::from_f64(re, im, PREC).expect("finite parts")
244 }
245
246 #[test]
247 fn sin_zero_is_zero() {
248 let s = BigComplex::zero(PREC).sin(PREC, MODE).expect("sin");
249 assert!(s.re().to_f64().abs() < 1e-12, "re = {}", s.re().to_f64());
250 assert!(s.im().to_f64().abs() < 1e-12, "im = {}", s.im().to_f64());
251 }
252
253 #[test]
254 fn cos_zero_is_one() {
255 let cz = BigComplex::zero(PREC).cos(PREC, MODE).expect("cos");
256 assert!(
257 (cz.re().to_f64() - 1.0).abs() < 1e-12,
258 "re = {}",
259 cz.re().to_f64()
260 );
261 assert!(cz.im().to_f64().abs() < 1e-12, "im = {}", cz.im().to_f64());
262 }
263
264 #[test]
265 fn cosh_zero_is_one() {
266 let cz = BigComplex::zero(PREC).cosh(PREC, MODE).expect("cosh");
267 assert!(
268 (cz.re().to_f64() - 1.0).abs() < 1e-12,
269 "re = {}",
270 cz.re().to_f64()
271 );
272 assert!(cz.im().to_f64().abs() < 1e-12, "im = {}", cz.im().to_f64());
273 }
274
275 #[test]
276 fn sinh_zero_is_zero() {
277 let s = BigComplex::zero(PREC).sinh(PREC, MODE).expect("sinh");
278 assert!(s.re().to_f64().abs() < 1e-12);
279 assert!(s.im().to_f64().abs() < 1e-12);
280 }
281
282 #[test]
283 fn pythagorean_identity_general() {
284 let z = c(0.5, 0.3);
286 let s = z.sin(PREC, MODE).expect("sin");
287 let co = z.cos(PREC, MODE).expect("cos");
288 let s2 = s.mul_core(&s);
289 let c2 = co.mul_core(&co);
290 let sum = &s2 + &c2;
291 assert!(
292 (sum.re().to_f64() - 1.0).abs() < 1e-9,
293 "re(sum) = {}",
294 sum.re().to_f64()
295 );
296 assert!(
297 sum.im().to_f64().abs() < 1e-9,
298 "im(sum) = {}",
299 sum.im().to_f64()
300 );
301 }
302
303 #[test]
304 fn tan_zero_is_zero() {
305 let t = BigComplex::zero(PREC).tan(PREC, MODE).expect("tan");
306 assert!(t.re().to_f64().abs() < 1e-12);
307 assert!(t.im().to_f64().abs() < 1e-12);
308 }
309
310 #[test]
311 fn tanh_zero_is_zero() {
312 let t = BigComplex::zero(PREC).tanh(PREC, MODE).expect("tanh");
313 assert!(t.re().to_f64().abs() < 1e-12);
314 assert!(t.im().to_f64().abs() < 1e-12);
315 }
316
317 #[test]
318 fn tan_matches_real_axis() {
319 let t = c(0.4, 0.0).tan(PREC, MODE).expect("tan");
321 let expected = 0.4_f64.tan();
322 assert!(
323 (t.re().to_f64() - expected).abs() < 1e-9,
324 "re = {}",
325 t.re().to_f64()
326 );
327 assert!(t.im().to_f64().abs() < 1e-9, "im = {}", t.im().to_f64());
328 }
329}