1use oxinum_core::{OxiNumError, OxiNumResult, Sign};
39
40use super::constants::pi;
41use super::float::{BigFloat, RoundingMode};
42
43fn half_angle_reduce(x: BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<(BigFloat, u32)> {
50 let one = BigFloat::from_i64(1, prec, mode);
51 let mut u = x;
52 let mut m = 0u32;
53
54 loop {
58 let abs_f64 = u.abs().to_f64();
60 if abs_f64 < f64::from_bits(0x3e00_0000_0000_0000u64) {
61 break;
63 }
64 if m >= 300 {
65 break;
67 }
68 let u_sq = u.mul_ref_with_mode(&u, mode).with_precision(prec, mode);
70 let one_plus_u_sq = one.add_ref_with_mode(&u_sq, mode);
71 let sqrt_val = one_plus_u_sq.sqrt(prec, mode)?;
72 let denom = one.add_ref_with_mode(&sqrt_val, mode);
73 u = u.div_ref_with_mode(&denom, mode)?;
74 u = u.with_precision(prec, mode);
75 m += 1;
76 }
77
78 Ok((u, m))
79}
80
81fn atan_taylor(u: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
93 if u.is_zero() {
94 return Ok(BigFloat::zero(prec));
95 }
96
97 let u_sq = u.mul_ref_with_mode(u, mode).with_precision(prec, mode);
98 let mut term = u.clone().with_precision(prec, mode);
100 let mut result = term.clone();
101
102 let n_terms: u64 = (prec as u64) / 64 + 8;
103
104 for k in 1..=n_terms {
105 let numer = term
107 .mul_ref_with_mode(&u_sq, mode)
108 .with_precision(prec, mode);
109 term = numer.neg().with_precision(prec, mode);
110 let denom_val = 2 * k + 1;
111 let denom_i64 = denom_val.min(i64::MAX as u64) as i64;
112 let denom_f = BigFloat::from_i64(denom_i64, prec, mode);
113 let scaled = term
114 .div_ref_with_mode(&denom_f, mode)
115 .map_err(|e| OxiNumError::Precision(format!("atan_taylor denom zero: {e}").into()))?;
116 result = result
117 .add_ref_with_mode(&scaled, mode)
118 .with_precision(prec, mode);
119 }
120
121 Ok(result.with_precision(prec, mode))
122}
123
124impl BigFloat {
129 pub fn atan(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
148 if self.is_nan() {
150 return Ok(BigFloat::nan(prec));
151 }
152 if self.is_infinite() {
153 let pi_val = pi(prec.saturating_add(16))?;
155 let two = BigFloat::from_i64(2, prec.saturating_add(16), mode);
156 let half_pi = pi_val.div_ref_with_mode(&two, mode)?;
157 return if self.sign == Sign::Negative {
158 Ok(half_pi.neg().with_precision(prec, mode))
159 } else {
160 Ok(half_pi.with_precision(prec, mode))
161 };
162 }
163
164 if self.is_zero() {
165 return Ok(BigFloat::zero(prec));
166 }
167
168 let work_prec = prec.saturating_add(64);
170
171 let orig_sign = self.signum();
173 let abs_x = self.abs().with_precision(work_prec, mode);
174
175 let one = BigFloat::from_i64(1, work_prec, mode);
176
177 let (working_x, use_complement): (BigFloat, bool) = if abs_x > one.clone() {
179 let inv = one.div_ref_with_mode(&abs_x, mode)?;
180 (inv.with_precision(work_prec, mode), true)
181 } else {
182 (abs_x, false)
183 };
184
185 let (reduced, m) = half_angle_reduce(working_x, work_prec, mode)?;
187
188 let mut result = atan_taylor(&reduced, work_prec, mode)?;
190
191 let two = BigFloat::from_i64(2, work_prec, mode);
193 for _ in 0..m {
194 result = result
195 .mul_ref_with_mode(&two, mode)
196 .with_precision(work_prec, mode);
197 }
198
199 if use_complement {
201 let pi_val = pi(work_prec)?;
202 let two_wp = BigFloat::from_i64(2, work_prec, mode);
203 let pi_over_2 = pi_val.div_ref_with_mode(&two_wp, mode)?;
204 result = pi_over_2
205 .sub_ref_with_mode(&result, mode)
206 .with_precision(work_prec, mode);
207 }
208
209 if orig_sign < 0 {
211 result = result.neg();
212 }
213
214 Ok(result.with_precision(prec, mode))
215 }
216
217 pub fn atan2(&self, x: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
242 let y = self;
243
244 if y.is_nan() || x.is_nan() {
247 return Ok(BigFloat::nan(prec));
248 }
249 if y.is_infinite() && x.is_infinite() {
251 let pi_val = pi(prec.saturating_add(16))?;
254 let four = BigFloat::from_i64(4, prec.saturating_add(16), mode);
255 let pi_over_4 = pi_val
256 .div_ref_with_mode(&four, mode)?
257 .with_precision(prec, mode);
258 let three_pi_over_4 = {
259 let three = BigFloat::from_i64(3, prec, mode);
260 three
261 .mul_ref_with_mode(&pi_over_4, mode)
262 .with_precision(prec, mode)
263 };
264 let (mag, apply_neg) = if x.sign == Sign::Negative {
265 (three_pi_over_4, y.sign == Sign::Negative)
266 } else {
267 (pi_over_4, y.sign == Sign::Negative)
268 };
269 return Ok(if apply_neg { mag.neg() } else { mag });
270 }
271 if y.is_infinite() {
273 let pi_val = pi(prec.saturating_add(16))?;
274 let two = BigFloat::from_i64(2, prec.saturating_add(16), mode);
275 let half_pi = pi_val
276 .div_ref_with_mode(&two, mode)?
277 .with_precision(prec, mode);
278 return if y.sign == Sign::Negative {
279 Ok(half_pi.neg())
280 } else {
281 Ok(half_pi)
282 };
283 }
284 if x.is_infinite() {
286 if x.sign == Sign::Positive {
287 return Ok(BigFloat::zero(prec));
288 } else {
289 let pi_val = pi(prec.saturating_add(16))?.with_precision(prec, mode);
291 return if y.sign == Sign::Negative {
292 Ok(pi_val.neg())
293 } else {
294 Ok(pi_val)
295 };
296 }
297 }
298
299 let y_sign = y.signum();
300 let x_sign = x.signum();
301
302 if y.is_zero() && x.is_zero() {
304 return Ok(BigFloat::zero(prec));
305 }
306
307 if x.is_zero() {
309 let pi_val = pi(prec.saturating_add(16))?;
310 let two = BigFloat::from_i64(2, prec.saturating_add(16), mode);
311 let pi_over_2 = pi_val.div_ref_with_mode(&two, mode)?;
312 return if y_sign >= 0 {
313 Ok(pi_over_2.with_precision(prec, mode))
314 } else {
315 Ok(pi_over_2.neg().with_precision(prec, mode))
316 };
317 }
318
319 if x_sign > 0 {
321 let work_prec = prec.saturating_add(16);
322 let ratio = y
323 .clone()
324 .with_precision(work_prec, mode)
325 .div_ref_with_mode(&x.clone().with_precision(work_prec, mode), mode)?;
326 return ratio.atan(prec, mode);
327 }
328
329 let work_prec = prec.saturating_add(32);
331 let ratio = y
332 .clone()
333 .with_precision(work_prec, mode)
334 .div_ref_with_mode(&x.clone().with_precision(work_prec, mode), mode)?;
335 let atan_val = ratio.atan(work_prec, mode)?;
336 let pi_val = pi(work_prec)?;
337
338 if y_sign >= 0 {
339 let result = pi_val.add_ref_with_mode(&atan_val, mode);
341 Ok(result.with_precision(prec, mode))
342 } else {
343 let result = atan_val.sub_ref_with_mode(&pi_val, mode);
345 Ok(result.with_precision(prec, mode))
346 }
347 }
348}
349
350#[cfg(test)]
355mod tests {
356 use super::*;
357
358 fn mk(n: i64, prec: u32) -> BigFloat {
359 BigFloat::from_i64(n, prec, RoundingMode::HalfEven)
360 }
361
362 #[test]
363 fn atan_zero() {
364 let z = mk(0, 64);
365 let r = z.atan(64, RoundingMode::HalfEven).expect("atan(0)");
366 assert!(r.is_zero());
367 }
368
369 #[test]
370 fn atan_one_is_pi_over_4() {
371 let prec = 64u32;
372 let one = mk(1, prec);
373 let a = one.atan(prec, RoundingMode::HalfEven).expect("atan(1)");
374 assert!(
375 (a.to_f64() - std::f64::consts::FRAC_PI_4).abs() < 1e-14,
376 "got {}",
377 a.to_f64()
378 );
379 }
380
381 #[test]
382 fn atan_minus_one() {
383 let prec = 64u32;
384 let minus_one = mk(-1, prec);
385 let a = minus_one
386 .atan(prec, RoundingMode::HalfEven)
387 .expect("atan(-1)");
388 assert!((a.to_f64() + std::f64::consts::FRAC_PI_4).abs() < 1e-14);
389 }
390
391 #[test]
392 fn atan2_quadrant_i() {
393 let prec = 64u32;
394 let mode = RoundingMode::HalfEven;
395 let one = mk(1, prec);
396 let a = one.atan2(&one, prec, mode).expect("atan2(1,1)");
397 assert!((a.to_f64() - std::f64::consts::FRAC_PI_4).abs() < 1e-14);
398 }
399
400 #[test]
401 fn atan2_negative_x() {
402 let prec = 64u32;
403 let mode = RoundingMode::HalfEven;
404 let one = mk(1, prec);
405 let neg_one = mk(-1, prec);
406 let a = one.atan2(&neg_one, prec, mode).expect("atan2(1,-1)");
408 let expected = 3.0 * std::f64::consts::FRAC_PI_4;
409 assert!(
410 (a.to_f64() - expected).abs() < 1e-13,
411 "got {}, expected {}",
412 a.to_f64(),
413 expected
414 );
415 }
416
417 #[test]
418 fn atan2_zero_x_positive_y() {
419 let prec = 64u32;
420 let mode = RoundingMode::HalfEven;
421 let one = mk(1, prec);
422 let zero = mk(0, prec);
423 let a = one.atan2(&zero, prec, mode).expect("atan2(1,0)");
424 assert!((a.to_f64() - std::f64::consts::FRAC_PI_2).abs() < 1e-14);
425 }
426
427 #[test]
428 fn atan2_zero_x_negative_y() {
429 let prec = 64u32;
430 let mode = RoundingMode::HalfEven;
431 let neg_one = mk(-1, prec);
432 let zero = mk(0, prec);
433 let a = neg_one.atan2(&zero, prec, mode).expect("atan2(-1,0)");
434 assert!((a.to_f64() + std::f64::consts::FRAC_PI_2).abs() < 1e-14);
435 }
436}