use num_complex::{Complex32, Complex64};
#[inline]
pub fn robust_complex_divide_f64(a: Complex64, b: Complex64) -> Complex64 {
let (ar, ai) = (a.re, a.im);
let (br, bi) = (b.re, b.im);
if br.is_infinite() || bi.is_infinite() {
if ar.is_finite() && ai.is_finite() {
return Complex64::new(
0.0 * ar.signum() * br.signum(),
-0.0 * ai.signum() * bi.signum(),
);
}
return Complex64::new(f64::NAN, f64::NAN);
}
let abs_ar = ar.abs();
let abs_ai = ai.abs();
let ab = if abs_ar >= abs_ai { abs_ar } else { abs_ai };
let abs_br = br.abs();
let abs_bi = bi.abs();
let cd = if abs_br >= abs_bi { abs_br } else { abs_bi };
if ab >= 0.5 * f64::MAX
|| ab <= f64::MIN_POSITIVE * 2.0 / f64::EPSILON
|| cd >= 0.5 * f64::MAX
|| cd <= f64::MIN_POSITIVE * 2.0 / f64::EPSILON
{
scaling_cdiv_f64(ar, ai, br, bi, ab, cd)
} else {
cdiv_f64(ar, ai, br, bi)
}
}
#[inline]
pub fn robust_complex_divide_f32(a: Complex32, b: Complex32) -> Complex32 {
let (ar, ai) = (f64::from(a.re), f64::from(a.im));
let (br, bi) = (f64::from(b.re), f64::from(b.im));
if br.is_infinite() || bi.is_infinite() {
if ar.is_finite() && ai.is_finite() {
return Complex32::new(
(0.0 * ar.signum() * br.signum()) as f32,
(-0.0 * ai.signum() * bi.signum()) as f32,
);
}
return Complex32::new(f32::NAN, f32::NAN);
}
let mag = 1.0 / br.mul_add(br, bi * bi);
let re = ar.mul_add(br, ai * bi);
let im = ai.mul_add(br, -ar * bi);
Complex32::new((re * mag) as f32, (im * mag) as f32)
}
#[inline]
fn cdiv_f64(a: f64, b: f64, c: f64, d: f64) -> Complex64 {
if d.abs() <= c.abs() {
robust_cdiv1(a, b, c, d)
} else {
let swapped = robust_cdiv1(b, a, d, c);
Complex64::new(swapped.re, -swapped.im)
}
}
#[inline]
fn scaling_cdiv_f64(a: f64, b: f64, c: f64, d: f64, ab: f64, cd: f64) -> Complex64 {
let (a, b, c, d, s) = scale_cdiv_args(a, b, c, d, ab, cd);
let quotient = cdiv_f64(a, b, c, d);
Complex64::new(quotient.re * s, quotient.im * s)
}
fn scale_cdiv_args(a: f64, b: f64, c: f64, d: f64, ab: f64, cd: f64) -> (f64, f64, f64, f64, f64) {
let half_ov = 0.5 * f64::MAX;
let two_un_eps = f64::MIN_POSITIVE * 2.0 / f64::EPSILON;
let big_scale = 2.0 / (f64::EPSILON * f64::EPSILON);
let mut s = 1.0;
let (mut a, mut b, mut c, mut d) = (a, b, c, d);
if ab >= half_ov {
a *= 0.5;
b *= 0.5;
s *= 2.0;
} else if ab <= two_un_eps {
a *= big_scale;
b *= big_scale;
s /= big_scale;
}
if cd >= half_ov {
c *= 0.5;
d *= 0.5;
s *= 0.5;
} else if cd <= two_un_eps {
c *= big_scale;
d *= big_scale;
s *= big_scale;
}
(a, b, c, d, s)
}
#[inline]
fn robust_cdiv1(a: f64, b: f64, c: f64, d: f64) -> Complex64 {
let r = d / c;
let t = 1.0 / (c + d * r);
Complex64::new(
robust_cdiv2(a, b, c, d, r, t),
robust_cdiv2(b, -a, c, d, r, t),
)
}
#[inline]
fn robust_cdiv2(a: f64, b: f64, c: f64, d: f64, r: f64, t: f64) -> f64 {
if r != 0.0 {
let br = b * r;
if br != 0.0 {
(a + br) * t
} else {
a * t + (b * t) * r
}
} else {
(a + d * (b / c)) * t
}
}
#[cfg(test)]
mod tests {
use super::*;
fn c64(re: f64, im: f64) -> Complex64 {
Complex64::new(re, im)
}
#[test]
fn normal_range_matches_the_textbook_formula() {
let a = c64(3.0, 4.0);
let b = c64(1.0, -2.0);
let expected = a / b;
let actual = robust_complex_divide_f64(a, b);
assert!((actual.re - expected.re).abs() <= 1e-15 * expected.re.abs().max(1.0));
assert!((actual.im - expected.im).abs() <= 1e-15 * expected.im.abs().max(1.0));
}
#[test]
fn huge_denominator_stays_representable() {
let scale = 2f64.powi(600);
let b = c64(scale, scale);
let actual = robust_complex_divide_f64(c64(1.0, 0.0), b);
let expected = 2f64.powi(-601);
assert!(
(actual.re - expected).abs() <= expected * 1e-15,
"{actual:?}"
);
assert!(
(actual.im + expected).abs() <= expected * 1e-15,
"{actual:?}"
);
}
#[test]
fn tiny_denominator_stays_representable() {
let scale = 2f64.powi(-600);
let b = c64(scale, scale);
let actual = robust_complex_divide_f64(c64(1.0, 0.0), b);
let expected = 2f64.powi(599);
assert!(
(actual.re - expected).abs() <= expected * 1e-15,
"{actual:?}"
);
assert!(
(actual.im + expected).abs() <= expected * 1e-15,
"{actual:?}"
);
}
#[test]
fn infinite_components_produce_signed_zeros() {
let actual = robust_complex_divide_f64(c64(1.0, 2.0), c64(f64::INFINITY, 1.0));
assert_eq!(actual.re, 0.0);
assert!(
actual.im.is_sign_negative() && actual.im == 0.0,
"{actual:?}"
);
}
#[test]
fn f32_widens_so_the_squares_cannot_overflow() {
let scale = 2f32.powi(60);
let actual =
robust_complex_divide_f32(Complex32::new(1.0, 0.0), Complex32::new(scale, scale));
let expected = 2f32.powi(-61);
assert!(
(actual.re - expected).abs() <= expected * 1e-6,
"{actual:?}"
);
assert!(
(actual.im + expected).abs() <= expected * 1e-6,
"{actual:?}"
);
}
}