1use core::str::FromStr;
34
35use oxinum_float::{atan2, cos, exp, ln, pow, sin, sqrt};
36
37use crate::{CBig, DBig, OxiNumError, OxiNumResult};
38
39const GUARD: usize = 10;
41
42fn make_dbig(s: &str) -> OxiNumResult<DBig> {
45 DBig::from_str(s).map_err(|e| OxiNumError::Parse(format!("{e}").into()))
46}
47
48fn exp_by_squaring_cbig(mut base: CBig, mut n: u32) -> CBig {
52 let mut result = CBig::one();
53 while n > 0 {
54 if n & 1 == 1 {
55 result = &result * &base;
56 }
57 base = &base * &base;
58 n >>= 1;
59 }
60 result
61}
62
63impl CBig {
64 pub fn abs(&self, precision: usize) -> OxiNumResult<DBig> {
76 if self.is_zero() {
77 return make_dbig("0.0");
78 }
79 sqrt(&self.norm_sqr(), precision)
80 }
81
82 pub fn arg(&self, precision: usize) -> OxiNumResult<DBig> {
89 atan2(&self.im, &self.re, precision)
90 }
91
92 pub fn exp(&self, precision: usize) -> OxiNumResult<CBig> {
100 let guard = precision + GUARD;
101 let ea = exp(&self.re, guard)?;
102 let cosb = cos(&self.im, guard)?;
103 let sinb = sin(&self.im, guard)?;
104 Ok(CBig::from_parts(&ea * &cosb, &ea * &sinb))
105 }
106
107 pub fn ln(&self, precision: usize) -> OxiNumResult<CBig> {
118 if self.is_zero() {
119 return Err(OxiNumError::Domain("ln(0) is undefined".into()));
120 }
121 let guard = precision + GUARD;
122 let ns = self.norm_sqr();
123 let re = &make_dbig("0.5")? * &ln(&ns, guard)?;
124 let im = atan2(&self.im, &self.re, guard)?;
125 Ok(CBig::from_parts(re, im))
126 }
127
128 pub fn sqrt(&self, precision: usize) -> OxiNumResult<CBig> {
141 let zero = DBig::from(0u32);
142
143 if self.is_zero() {
144 return Ok(CBig::zero());
145 }
146
147 if self.im == zero {
149 if self.re >= zero {
150 return Ok(CBig::from_parts(sqrt(&self.re, precision)?, zero));
152 }
153 let neg_a = -&self.re;
155 return Ok(CBig::from_parts(zero, sqrt(&neg_a, precision)?));
156 }
157
158 let guard = precision + GUARD;
160 let m = sqrt(&self.norm_sqr(), guard)?; let two = make_dbig("2.0")?;
162
163 let mut r_re = &(&m + &self.re) / &two;
165 if r_re < zero {
166 r_re = DBig::from(0u32);
167 }
168
169 let mut r_im = &(&m - &self.re) / &two;
171 if r_im < zero {
172 r_im = DBig::from(0u32);
173 }
174
175 let re = sqrt(&r_re, precision)?;
176 let im_mag = sqrt(&r_im, precision)?;
177
178 let im = if self.im < zero { -im_mag } else { im_mag };
180
181 Ok(CBig::from_parts(re, im))
182 }
183
184 pub fn pow(&self, w: &CBig, precision: usize) -> OxiNumResult<CBig> {
194 if self.is_zero() {
195 return if w.is_zero() {
196 Ok(CBig::one())
197 } else {
198 Ok(CBig::zero())
199 };
200 }
201 let guard = precision + GUARD;
202 let lz = self.ln(guard)?;
203 let prod = w * &lz;
204 prod.exp(precision)
205 }
206
207 pub fn from_polar(r: &DBig, theta: &DBig, precision: usize) -> OxiNumResult<CBig> {
213 let guard = precision.saturating_add(GUARD);
214 let cos_t = cos(theta, guard)?;
215 let sin_t = sin(theta, guard)?;
216 Ok(CBig::from_parts(r * &cos_t, r * &sin_t))
217 }
218
219 pub fn to_polar(&self, precision: usize) -> OxiNumResult<(DBig, DBig)> {
225 let mag = self.abs(precision)?;
226 let arg = self.arg(precision)?;
227 Ok((mag, arg))
228 }
229
230 pub fn powi(&self, n: i32, _precision: usize) -> OxiNumResult<CBig> {
239 if n == 0 {
240 return Ok(CBig::one());
241 }
242 let negative = n < 0;
243 let abs_n = n.unsigned_abs();
244 let result = exp_by_squaring_cbig(self.clone(), abs_n);
245 if negative {
246 CBig::one().checked_div(&result)
247 } else {
248 Ok(result)
249 }
250 }
251
252 pub fn powf(&self, exp: &DBig, precision: usize) -> OxiNumResult<CBig> {
262 let zero = DBig::from(0u32);
263 if self.is_zero() {
264 return if *exp == zero {
265 Ok(CBig::one())
266 } else {
267 Ok(CBig::zero())
268 };
269 }
270 let guard = precision.saturating_add(GUARD);
271 let r = self.abs(guard)?;
272 let theta = self.arg(guard)?;
273 let rx = pow(&r, exp, guard)?;
275 let x_theta = exp * θ
277 let re = &rx * &cos(&x_theta, guard)?;
278 let im = &rx * &sin(&x_theta, guard)?;
279 Ok(CBig::from_parts(re, im))
280 }
281}
282
283#[cfg(test)]
288mod tests {
289 use super::*;
290 use oxinum_float::compute_pi;
291
292 fn pi(precision: usize) -> DBig {
294 compute_pi(precision)
295 }
296
297 #[test]
298 fn exp_i_pi_is_minus_one() {
299 let z = CBig::from_parts(DBig::from(0u32), pi(50));
301 let r = z.exp(40).expect("exp");
302 let (re, im) = r.to_f64_parts();
303 assert!((re + 1.0).abs() < 1e-12, "re = {re}");
304 assert!(im.abs() < 1e-12, "im = {im}");
305 }
306
307 #[test]
308 fn ln_minus_one_is_i_pi() {
309 let z = CBig::from_real(make_dbig("-1.0").expect("literal"));
310 let r = z.ln(40).expect("ln");
311 let (re, im) = r.to_f64_parts();
312 assert!(re.abs() < 1e-12, "re = {re}");
313 assert!((im - std::f64::consts::PI).abs() < 1e-12, "im = {im}");
314 }
315
316 #[test]
317 fn ln_zero_is_domain_error() {
318 let r = CBig::zero().ln(40);
319 assert!(matches!(r, Err(OxiNumError::Domain(_))), "got {r:?}");
320 }
321
322 #[test]
323 fn sqrt_minus_one_is_i() {
324 let z = CBig::from_real(make_dbig("-1.0").expect("literal"));
325 let r = z.sqrt(40).expect("sqrt");
326 let (re, im) = r.to_f64_parts();
327 assert!(re.abs() < 1e-12, "re = {re}");
328 assert!((im - 1.0).abs() < 1e-12, "im = {im}");
329 }
330
331 #[test]
332 fn sqrt_two_i_is_one_plus_i() {
333 let z = CBig::from_parts(DBig::from(0u32), DBig::from(2u32));
335 let r = z.sqrt(40).expect("sqrt");
336 let (re, im) = r.to_f64_parts();
337 assert!((re - 1.0).abs() < 1e-12, "re = {re}");
338 assert!((im - 1.0).abs() < 1e-12, "im = {im}");
339 }
340
341 #[test]
342 fn sqrt_positive_real() {
343 let z = CBig::from_real(DBig::from(4u32));
345 let r = z.sqrt(40).expect("sqrt");
346 let (re, im) = r.to_f64_parts();
347 assert!((re - 2.0).abs() < 1e-12, "re = {re}");
348 assert!(im.abs() < 1e-12, "im = {im}");
349 }
350
351 #[test]
352 fn abs_three_four_is_five() {
353 let z = CBig::from_parts(DBig::from(3u32), DBig::from(4u32));
354 let m = z.abs(40).expect("abs");
355 assert!(m.to_string().starts_with('5'), "|3+4i| = {m}");
356 }
357
358 #[test]
359 fn abs_zero_is_zero() {
360 let m = CBig::zero().abs(40).expect("abs");
361 assert_eq!(m, DBig::from(0u32));
362 }
363
364 #[test]
365 fn arg_of_i_is_half_pi() {
366 let a = CBig::i().arg(40).expect("arg");
367 let v = a.to_f64();
368 assert!(
369 (v.value() - std::f64::consts::FRAC_PI_2).abs() < 1e-12,
370 "arg(i) = {a}"
371 );
372 }
373
374 #[test]
375 fn pow_i_squared_is_minus_one() {
376 let r = CBig::i()
378 .pow(&CBig::from_real(DBig::from(2u32)), 40)
379 .expect("pow");
380 let (re, im) = r.to_f64_parts();
381 assert!((re + 1.0).abs() < 1e-12, "re = {re}");
382 assert!(im.abs() < 1e-12, "im = {im}");
383 }
384
385 #[test]
386 fn pow_to_one_is_identity() {
387 let z = CBig::from_real(make_dbig("2.0").expect("literal"));
390 let r = z.pow(&CBig::one(), 40).expect("pow");
391 let (re, im) = r.to_f64_parts();
392 assert!((re - 2.0).abs() < 1e-12, "re = {re}");
393 assert!(im.abs() < 1e-12, "im = {im}");
394 }
395
396 #[test]
397 fn pow_zero_zero_is_one() {
398 let r = CBig::zero().pow(&CBig::zero(), 40).expect("pow");
399 assert!(r == CBig::one());
400 }
401
402 #[test]
403 fn pow_zero_base_nonzero_exp_is_zero() {
404 let r = CBig::zero()
405 .pow(&CBig::from_parts(DBig::from(2u32), DBig::from(1u32)), 40)
406 .expect("pow");
407 assert!(r.is_zero());
408 }
409
410 #[test]
413 fn from_polar_two_half_pi_is_2i() {
414 let r = DBig::from(2u32);
416 let theta = &pi(50) / &make_dbig("2.0").expect("2.0");
417 let z = CBig::from_polar(&r, &theta, 40).expect("from_polar");
418 let (re, im) = z.to_f64_parts();
419 assert!(re.abs() < 1e-9, "re = {re}");
420 assert!((im - 2.0).abs() < 1e-9, "im = {im}");
421 }
422
423 #[test]
424 fn to_polar_three_four_is_5_atan2_4_3() {
425 let z = CBig::from_f64(3.0, 4.0).expect("finite");
429 let (mag, arg) = z.to_polar(40).expect("to_polar");
430 assert!((mag.to_f64().value() - 5.0).abs() < 1e-9, "mag = {mag}");
431 let expected_arg = (4.0_f64).atan2(3.0);
432 assert!(
433 (arg.to_f64().value() - expected_arg).abs() < 1e-9,
434 "arg = {arg}"
435 );
436 }
437
438 #[test]
439 fn from_polar_to_polar_roundtrip() {
440 let z = CBig::from_f64(2.0, 3.0).expect("finite");
444 let (mag, arg) = z.to_polar(50).expect("to_polar");
445 let z2 = CBig::from_polar(&mag, &arg, 40).expect("from_polar");
446 let (re, im) = z2.to_f64_parts();
447 assert!((re - 2.0).abs() < 1e-9, "re = {re}");
448 assert!((im - 3.0).abs() < 1e-9, "im = {im}");
449 }
450
451 #[test]
454 fn powi_zero_exponent_is_one() {
455 let z = CBig::from_f64(1.5, 0.7).expect("finite");
456 let r = z.powi(0, 40).expect("powi");
457 assert!(r == CBig::one());
458 }
459
460 #[test]
461 fn powi_one_plus_i_squared() {
462 let z = CBig::from_f64(1.0, 1.0).expect("finite");
464 let r = z.powi(2, 40).expect("powi");
465 let (re, im) = r.to_f64_parts();
466 assert!(re.abs() < 1e-9, "re = {re}");
467 assert!((im - 2.0).abs() < 1e-9, "im = {im}");
468 }
469
470 #[test]
471 fn powi_i_fourth_is_one() {
472 let r = CBig::i().powi(4, 40).expect("powi");
474 let (re, im) = r.to_f64_parts();
475 assert!((re - 1.0).abs() < 1e-9, "re = {re}");
476 assert!(im.abs() < 1e-9, "im = {im}");
477 }
478
479 #[test]
480 fn powi_negative_one_is_reciprocal() {
481 let z = CBig::from_real(DBig::from(2u32));
483 let r = z.powi(-1, 40).expect("powi");
484 let (re, im) = r.to_f64_parts();
485 assert!((re - 0.5).abs() < 1e-9, "re = {re}");
486 assert!(im.abs() < 1e-9, "im = {im}");
487 }
488
489 #[test]
490 fn powf_matches_powi_squared() {
491 let z = CBig::from_f64(1.5, 0.7).expect("finite");
493 let exp = DBig::from(2u32);
494 let r1 = z.powf(&exp, 40).expect("powf");
495 let r2 = z.powi(2, 40).expect("powi");
496 let (re1, im1) = r1.to_f64_parts();
497 let (re2, im2) = r2.to_f64_parts();
498 assert!((re1 - re2).abs() < 1e-9, "re: {re1} vs {re2}");
499 assert!((im1 - im2).abs() < 1e-9, "im: {im1} vs {im2}");
500 }
501
502 #[test]
503 fn powf_matches_pow_on_real() {
504 let z = CBig::from_real(DBig::from(2u32));
506 let exp = DBig::from(3u32);
507 let r = z.powf(&exp, 40).expect("powf");
508 let (re, im) = r.to_f64_parts();
509 assert!((re - 8.0).abs() < 1e-9, "re = {re}");
510 assert!(im.abs() < 1e-9, "im = {im}");
511 }
512}