1use oxinum_core::OxiNumError;
30use oxinum_core::OxiNumResult;
31use oxinum_float::native::{BigFloat, RoundingMode};
32
33use super::BigComplex;
34
35const GUARD: u32 = 10;
37
38fn exp_by_squaring_native(mut base: BigComplex, mut n: u32) -> BigComplex {
44 let prec = base.re.precision();
45 let mode = RoundingMode::HalfEven;
46 let mut result = BigComplex::one(prec, mode);
47 while n > 0 {
48 if n & 1 == 1 {
49 result = result.mul_core(&base);
50 }
51 base = base.mul_core(&base);
52 n >>= 1;
53 }
54 result
55}
56
57impl BigComplex {
58 pub fn abs(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
77 if self.is_zero() {
78 return Ok(BigFloat::zero(prec));
79 }
80 let guard = prec.saturating_add(GUARD);
81 let nrm = self.norm_sqr().with_precision(guard, mode);
82 Ok(nrm.sqrt(guard, mode)?.with_precision(prec, mode))
83 }
84
85 pub fn arg(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
101 let guard = prec.saturating_add(GUARD);
102 let re = self.re.clone().with_precision(guard, mode);
103 let im = self.im.clone().with_precision(guard, mode);
104 Ok(im.atan2(&re, guard, mode)?.with_precision(prec, mode))
105 }
106
107 pub fn exp(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
125 let guard = prec.saturating_add(GUARD);
126 let a = self.re.clone().with_precision(guard, mode);
127 let b = self.im.clone().with_precision(guard, mode);
128
129 let exp_a = a.exp(guard, mode)?;
130 let cos_b = b.cos(guard, mode)?;
131 let sin_b = b.sin(guard, mode)?;
132
133 let re = (&exp_a * &cos_b).with_precision(prec, mode);
134 let im = (&exp_a * &sin_b).with_precision(prec, mode);
135 Ok(BigComplex { re, im })
136 }
137
138 pub fn ln(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
157 if self.is_zero() {
158 return Err(OxiNumError::Domain("ln(0) is undefined".into()));
159 }
160 let guard = prec.saturating_add(GUARD);
161 let a = self.re.clone().with_precision(guard, mode);
162 let b = self.im.clone().with_precision(guard, mode);
163
164 let nrm = self.norm_sqr().with_precision(guard, mode);
166 let ln_nrm = nrm.ln(guard, mode)?;
167 let half = BigFloat::from_f64(0.5, guard)?;
168 let re = (&ln_nrm * &half).with_precision(prec, mode);
169
170 let im = b.atan2(&a, guard, mode)?.with_precision(prec, mode);
172
173 Ok(BigComplex { re, im })
174 }
175
176 pub fn sqrt(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
200 if self.is_zero() {
201 return Ok(BigComplex::zero(prec));
202 }
203 let guard = prec.saturating_add(GUARD);
204
205 if self.im.is_zero() {
207 let a = self.re.clone().with_precision(guard, mode);
208 if !a.is_sign_negative() {
209 let re = a.sqrt(guard, mode)?.with_precision(prec, mode);
211 return Ok(BigComplex {
212 re,
213 im: BigFloat::zero(prec),
214 });
215 } else {
216 let neg_a = (-&a).with_precision(guard, mode);
218 let im = neg_a.sqrt(guard, mode)?.with_precision(prec, mode);
219 return Ok(BigComplex {
220 re: BigFloat::zero(prec),
221 im,
222 });
223 }
224 }
225
226 let a = self.re.clone().with_precision(guard, mode);
228 let two = BigFloat::from_i64(2, guard, mode);
229
230 let m = {
232 let nrm = self.norm_sqr().with_precision(guard, mode);
233 nrm.sqrt(guard, mode)?
234 };
235
236 let zero = BigFloat::zero(guard);
237
238 let re_radicand = {
240 let s = &m + &a;
241 let r = s.div_ref_with_mode(&two, mode)?;
242 if r < zero {
243 zero.clone()
244 } else {
245 r
246 }
247 };
248 let re = re_radicand.sqrt(guard, mode)?.with_precision(prec, mode);
249
250 let im_radicand = {
252 let s = &m - &a;
253 let r = s.div_ref_with_mode(&two, mode)?;
254 if r < zero {
255 zero.clone()
256 } else {
257 r
258 }
259 };
260 let im_mag = im_radicand.sqrt(guard, mode)?;
261
262 let im = if self.im.is_sign_negative() {
264 (-&im_mag).with_precision(prec, mode)
265 } else {
266 im_mag.with_precision(prec, mode)
267 };
268
269 Ok(BigComplex { re, im })
270 }
271
272 pub fn pow(&self, w: &BigComplex, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
293 if self.is_zero() {
294 return if w.is_zero() {
295 Ok(BigComplex::one(prec, mode))
296 } else {
297 Ok(BigComplex::zero(prec))
298 };
299 }
300 let guard = prec.saturating_add(GUARD);
301 let ln_z = self.ln(guard, mode)?;
302 let prod = w.mul_core(&ln_z);
303 prod.exp(prec, mode)
304 }
305
306 pub fn from_polar(
312 r: &BigFloat,
313 theta: &BigFloat,
314 prec: u32,
315 mode: RoundingMode,
316 ) -> OxiNumResult<BigComplex> {
317 let guard = prec.saturating_add(GUARD);
318 let r_g = r.clone().with_precision(guard, mode);
319 let theta_g = theta.clone().with_precision(guard, mode);
320 let cos_t = theta_g.cos(guard, mode)?;
321 let sin_t = theta_g.sin(guard, mode)?;
322 let re = (&r_g * &cos_t).with_precision(prec, mode);
323 let im = (&r_g * &sin_t).with_precision(prec, mode);
324 Ok(BigComplex { re, im })
325 }
326
327 pub fn to_polar(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<(BigFloat, BigFloat)> {
333 let mag = self.abs(prec, mode)?;
334 let arg = self.arg(prec, mode)?;
335 Ok((mag, arg))
336 }
337
338 pub fn powi(&self, n: i32, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
347 if n == 0 {
348 return Ok(BigComplex::one(prec, mode));
349 }
350 let negative = n < 0;
351 let abs_n = n.unsigned_abs();
352 let result = exp_by_squaring_native(self.clone(), abs_n);
353 if negative {
354 BigComplex::one(prec, mode)
356 .checked_div(&result, prec, mode)
357 .map_err(|_| OxiNumError::DivByZero)
358 } else {
359 Ok(result)
360 }
361 }
362
363 pub fn powf(&self, exp: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
373 if self.is_zero() {
374 if exp.is_zero() {
375 return Ok(BigComplex::one(prec, mode));
376 } else {
377 return Ok(BigComplex::zero(prec));
378 }
379 }
380 let guard = prec.saturating_add(GUARD);
381 let r = self.abs(guard, mode)?;
382 let theta = self.arg(guard, mode)?;
383 let exp_g = exp.clone().with_precision(guard, mode);
384 let rx = r.pow(&exp_g, guard, mode)?;
386 let x_theta = (&exp_g * &theta).with_precision(guard, mode);
388 let cos_val = x_theta.cos(guard, mode)?;
389 let sin_val = x_theta.sin(guard, mode)?;
390 let re = (&rx * &cos_val).with_precision(prec, mode);
391 let im = (&rx * &sin_val).with_precision(prec, mode);
392 Ok(BigComplex { re, im })
393 }
394}
395
396#[cfg(test)]
401mod tests {
402 use super::*;
403
404 const PREC: u32 = 80;
405 const MODE: RoundingMode = RoundingMode::HalfEven;
406
407 fn c(re: f64, im: f64) -> BigComplex {
408 BigComplex::from_f64(re, im, PREC).expect("finite parts")
409 }
410
411 #[test]
412 fn abs_three_four_is_five() {
413 let m = c(3.0, 4.0).abs(PREC, MODE).expect("abs");
414 assert!((m.to_f64() - 5.0).abs() < 1e-9, "|3+4i| = {}", m.to_f64());
415 }
416
417 #[test]
418 fn abs_zero_is_zero() {
419 let m = BigComplex::zero(PREC).abs(PREC, MODE).expect("abs");
420 assert!(m.is_zero());
421 }
422
423 #[test]
424 fn arg_of_i_is_half_pi() {
425 let a = BigComplex::i(PREC, MODE).arg(PREC, MODE).expect("arg");
426 assert!(
427 (a.to_f64() - std::f64::consts::FRAC_PI_2).abs() < 1e-9,
428 "arg(i) = {}",
429 a.to_f64()
430 );
431 }
432
433 #[test]
434 fn exp_i_pi_is_minus_one() {
435 let z = c(0.0, std::f64::consts::PI);
437 let e = z.exp(PREC, MODE).expect("exp");
438 assert!(
439 (e.re().to_f64() + 1.0).abs() < 1e-9,
440 "re = {}",
441 e.re().to_f64()
442 );
443 assert!(e.im().to_f64().abs() < 1e-9, "im = {}", e.im().to_f64());
444 }
445
446 #[test]
447 fn ln_minus_one_is_i_pi() {
448 let l = c(-1.0, 0.0).ln(PREC, MODE).expect("ln");
449 assert!(l.re().to_f64().abs() < 1e-9, "re = {}", l.re().to_f64());
450 assert!(
451 (l.im().to_f64() - std::f64::consts::PI).abs() < 1e-9,
452 "im = {}",
453 l.im().to_f64()
454 );
455 }
456
457 #[test]
458 fn ln_zero_is_domain_error() {
459 let l = BigComplex::zero(PREC).ln(PREC, MODE);
460 assert!(matches!(l, Err(OxiNumError::Domain(_))), "got {l:?}");
461 }
462
463 #[test]
464 fn sqrt_minus_one_is_i() {
465 let r = c(-1.0, 0.0).sqrt(PREC, MODE).expect("sqrt");
466 assert!(r.re().to_f64().abs() < 1e-9, "re = {}", r.re().to_f64());
467 assert!(
468 (r.im().to_f64() - 1.0).abs() < 1e-9,
469 "im = {}",
470 r.im().to_f64()
471 );
472 }
473
474 #[test]
475 fn sqrt_two_i_is_one_plus_i() {
476 let r = c(0.0, 2.0).sqrt(PREC, MODE).expect("sqrt");
477 assert!(
478 (r.re().to_f64() - 1.0).abs() < 1e-9,
479 "re = {}",
480 r.re().to_f64()
481 );
482 assert!(
483 (r.im().to_f64() - 1.0).abs() < 1e-9,
484 "im = {}",
485 r.im().to_f64()
486 );
487 }
488
489 #[test]
490 fn sqrt_positive_real() {
491 let r = c(4.0, 0.0).sqrt(PREC, MODE).expect("sqrt");
493 assert!((r.re().to_f64() - 2.0).abs() < 1e-9);
494 assert!(r.im().to_f64().abs() < 1e-12);
495 }
496
497 #[test]
498 fn sqrt_squared_roundtrip() {
499 let z = c(2.0, -3.0);
501 let r = z.sqrt(PREC, MODE).expect("sqrt");
502 let sq = r.mul_core(&r);
503 assert!(
504 (sq.re().to_f64() - 2.0).abs() < 1e-9,
505 "re = {}",
506 sq.re().to_f64()
507 );
508 assert!(
509 (sq.im().to_f64() + 3.0).abs() < 1e-9,
510 "im = {}",
511 sq.im().to_f64()
512 );
513 }
514
515 #[test]
516 fn pow_i_squared_is_minus_one() {
517 let r = BigComplex::i(PREC, MODE)
518 .pow(&c(2.0, 0.0), PREC, MODE)
519 .expect("pow");
520 assert!(
521 (r.re().to_f64() + 1.0).abs() < 1e-9,
522 "re = {}",
523 r.re().to_f64()
524 );
525 assert!(r.im().to_f64().abs() < 1e-9, "im = {}", r.im().to_f64());
526 }
527
528 #[test]
529 fn pow_zero_zero_is_one() {
530 let r = BigComplex::zero(PREC)
531 .pow(&BigComplex::zero(PREC), PREC, MODE)
532 .expect("pow");
533 assert!((r.re().to_f64() - 1.0).abs() < 1e-12);
534 assert!(r.im().to_f64().abs() < 1e-12);
535 }
536
537 #[test]
538 fn pow_zero_base_nonzero_exp_is_zero() {
539 let r = BigComplex::zero(PREC)
540 .pow(&c(2.0, 1.0), PREC, MODE)
541 .expect("pow");
542 assert!(r.is_zero());
543 }
544
545 #[test]
548 fn from_polar_two_half_pi_is_2i() {
549 let r = BigFloat::from_f64(2.0, PREC).expect("finite");
551 let half_pi = BigFloat::from_f64(std::f64::consts::FRAC_PI_2, PREC).expect("pi/2");
552 let z = BigComplex::from_polar(&r, &half_pi, PREC, MODE).expect("from_polar");
553 assert!(z.re().to_f64().abs() < 1e-9, "re = {}", z.re().to_f64());
554 assert!(
555 (z.im().to_f64() - 2.0).abs() < 1e-9,
556 "im = {}",
557 z.im().to_f64()
558 );
559 }
560
561 #[test]
562 fn to_polar_three_four_is_5_atan2_4_3() {
563 const P: u32 = 53;
567 let z = BigComplex::from_parts(
568 BigFloat::from_i64(3, P, MODE),
569 BigFloat::from_i64(4, P, MODE),
570 );
571 let (mag, arg) = z.to_polar(P, MODE).expect("to_polar");
572 assert!((mag.to_f64() - 5.0).abs() < 1e-9, "mag = {}", mag.to_f64());
573 let expected_arg = 4.0_f64.atan2(3.0);
574 assert!(
575 (arg.to_f64() - expected_arg).abs() < 1e-9,
576 "arg = {}",
577 arg.to_f64()
578 );
579 }
580
581 #[test]
582 fn from_polar_to_polar_roundtrip() {
583 const P: u32 = 53;
588 let z = BigComplex::from_parts(
589 BigFloat::from_i64(2, P, MODE),
590 BigFloat::from_i64(3, P, MODE),
591 );
592 let (mag, arg) = z.to_polar(P, MODE).expect("to_polar");
593 let z2 = BigComplex::from_polar(&mag, &arg, P, MODE).expect("from_polar");
594 assert!(
595 (z2.re().to_f64() - 2.0).abs() < 1e-9,
596 "re = {}",
597 z2.re().to_f64()
598 );
599 assert!(
600 (z2.im().to_f64() - 3.0).abs() < 1e-9,
601 "im = {}",
602 z2.im().to_f64()
603 );
604 }
605
606 #[test]
609 fn powi_zero_exponent_is_one() {
610 let z = c(1.5, 0.7);
611 let r = z.powi(0, PREC, MODE).expect("powi");
612 assert!(
613 (r.re().to_f64() - 1.0).abs() < 1e-12,
614 "re = {}",
615 r.re().to_f64()
616 );
617 assert!(r.im().to_f64().abs() < 1e-12, "im = {}", r.im().to_f64());
618 }
619
620 #[test]
621 fn powi_one_plus_i_squared() {
622 let z = c(1.0, 1.0);
624 let r = z.powi(2, PREC, MODE).expect("powi");
625 assert!(r.re().to_f64().abs() < 1e-9, "re = {}", r.re().to_f64());
626 assert!(
627 (r.im().to_f64() - 2.0).abs() < 1e-9,
628 "im = {}",
629 r.im().to_f64()
630 );
631 }
632
633 #[test]
634 fn powi_i_fourth_is_one() {
635 let r = BigComplex::i(PREC, MODE).powi(4, PREC, MODE).expect("powi");
637 assert!(
638 (r.re().to_f64() - 1.0).abs() < 1e-9,
639 "re = {}",
640 r.re().to_f64()
641 );
642 assert!(r.im().to_f64().abs() < 1e-9, "im = {}", r.im().to_f64());
643 }
644
645 #[test]
646 fn powi_negative_one_is_reciprocal() {
647 let z = c(2.0, 0.0);
649 let r = z.powi(-1, PREC, MODE).expect("powi");
650 assert!(
651 (r.re().to_f64() - 0.5).abs() < 1e-9,
652 "re = {}",
653 r.re().to_f64()
654 );
655 assert!(r.im().to_f64().abs() < 1e-9, "im = {}", r.im().to_f64());
656 }
657
658 #[test]
659 fn powf_matches_powi_squared() {
660 let z = c(1.5, 0.7);
662 let exp = BigFloat::from_f64(2.0, PREC).expect("finite");
663 let r1 = z.powf(&exp, PREC, MODE).expect("powf");
664 let r2 = z.powi(2, PREC, MODE).expect("powi");
665 assert!(
666 (r1.re().to_f64() - r2.re().to_f64()).abs() < 1e-9,
667 "re: {} vs {}",
668 r1.re().to_f64(),
669 r2.re().to_f64()
670 );
671 assert!(
672 (r1.im().to_f64() - r2.im().to_f64()).abs() < 1e-9,
673 "im: {} vs {}",
674 r1.im().to_f64(),
675 r2.im().to_f64()
676 );
677 }
678
679 #[test]
680 fn powf_matches_pow_on_real() {
681 let z = c(2.0, 0.0);
683 let exp = BigFloat::from_f64(3.0, PREC).expect("finite");
684 let r = z.powf(&exp, PREC, MODE).expect("powf");
685 assert!(
686 (r.re().to_f64() - 8.0).abs() < 1e-9,
687 "re = {}",
688 r.re().to_f64()
689 );
690 assert!(r.im().to_f64().abs() < 1e-9, "im = {}", r.im().to_f64());
691 }
692}