1use num_complex::{Complex32, Complex64};
19
20#[inline]
23pub fn robust_complex_divide_f64(a: Complex64, b: Complex64) -> Complex64 {
24 let (ar, ai) = (a.re, a.im);
25 let (br, bi) = (b.re, b.im);
26 if br.is_infinite() || bi.is_infinite() {
27 if ar.is_finite() && ai.is_finite() {
28 return Complex64::new(
29 0.0 * ar.signum() * br.signum(),
30 -0.0 * ai.signum() * bi.signum(),
31 );
32 }
33 return Complex64::new(f64::NAN, f64::NAN);
34 }
35 let abs_ar = ar.abs();
38 let abs_ai = ai.abs();
39 let ab = if abs_ar >= abs_ai { abs_ar } else { abs_ai };
40 let abs_br = br.abs();
41 let abs_bi = bi.abs();
42 let cd = if abs_br >= abs_bi { abs_br } else { abs_bi };
43 if ab >= 0.5 * f64::MAX
44 || ab <= f64::MIN_POSITIVE * 2.0 / f64::EPSILON
45 || cd >= 0.5 * f64::MAX
46 || cd <= f64::MIN_POSITIVE * 2.0 / f64::EPSILON
47 {
48 scaling_cdiv_f64(ar, ai, br, bi, ab, cd)
49 } else {
50 cdiv_f64(ar, ai, br, bi)
51 }
52}
53
54#[inline]
57pub fn robust_complex_divide_f32(a: Complex32, b: Complex32) -> Complex32 {
58 let (ar, ai) = (f64::from(a.re), f64::from(a.im));
59 let (br, bi) = (f64::from(b.re), f64::from(b.im));
60 if br.is_infinite() || bi.is_infinite() {
61 if ar.is_finite() && ai.is_finite() {
62 return Complex32::new(
63 (0.0 * ar.signum() * br.signum()) as f32,
64 (-0.0 * ai.signum() * bi.signum()) as f32,
65 );
66 }
67 return Complex32::new(f32::NAN, f32::NAN);
68 }
69 let mag = 1.0 / br.mul_add(br, bi * bi);
70 let re = ar.mul_add(br, ai * bi);
71 let im = ai.mul_add(br, -ar * bi);
72 Complex32::new((re * mag) as f32, (im * mag) as f32)
73}
74
75#[inline]
76fn cdiv_f64(a: f64, b: f64, c: f64, d: f64) -> Complex64 {
77 if d.abs() <= c.abs() {
78 robust_cdiv1(a, b, c, d)
79 } else {
80 let swapped = robust_cdiv1(b, a, d, c);
81 Complex64::new(swapped.re, -swapped.im)
82 }
83}
84
85#[inline]
86fn scaling_cdiv_f64(a: f64, b: f64, c: f64, d: f64, ab: f64, cd: f64) -> Complex64 {
87 let (a, b, c, d, s) = scale_cdiv_args(a, b, c, d, ab, cd);
88 let quotient = cdiv_f64(a, b, c, d);
89 Complex64::new(quotient.re * s, quotient.im * s)
90}
91
92fn scale_cdiv_args(a: f64, b: f64, c: f64, d: f64, ab: f64, cd: f64) -> (f64, f64, f64, f64, f64) {
93 let half_ov = 0.5 * f64::MAX;
94 let two_un_eps = f64::MIN_POSITIVE * 2.0 / f64::EPSILON;
95 let big_scale = 2.0 / (f64::EPSILON * f64::EPSILON);
96 let mut s = 1.0;
97 let (mut a, mut b, mut c, mut d) = (a, b, c, d);
98 if ab >= half_ov {
99 a *= 0.5;
100 b *= 0.5;
101 s *= 2.0;
102 } else if ab <= two_un_eps {
103 a *= big_scale;
104 b *= big_scale;
105 s /= big_scale;
106 }
107 if cd >= half_ov {
108 c *= 0.5;
109 d *= 0.5;
110 s *= 0.5;
111 } else if cd <= two_un_eps {
112 c *= big_scale;
113 d *= big_scale;
114 s *= big_scale;
115 }
116 (a, b, c, d, s)
117}
118
119#[inline]
120fn robust_cdiv1(a: f64, b: f64, c: f64, d: f64) -> Complex64 {
121 let r = d / c;
122 let t = 1.0 / (c + d * r);
123 Complex64::new(
124 robust_cdiv2(a, b, c, d, r, t),
125 robust_cdiv2(b, -a, c, d, r, t),
126 )
127}
128
129#[inline]
130fn robust_cdiv2(a: f64, b: f64, c: f64, d: f64, r: f64, t: f64) -> f64 {
131 if r != 0.0 {
132 let br = b * r;
133 if br != 0.0 {
134 (a + br) * t
135 } else {
136 a * t + (b * t) * r
137 }
138 } else {
139 (a + d * (b / c)) * t
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 fn c64(re: f64, im: f64) -> Complex64 {
149 Complex64::new(re, im)
150 }
151
152 #[test]
153 fn normal_range_matches_the_textbook_formula() {
154 let a = c64(3.0, 4.0);
155 let b = c64(1.0, -2.0);
156 let expected = a / b;
157 let actual = robust_complex_divide_f64(a, b);
158 assert!((actual.re - expected.re).abs() <= 1e-15 * expected.re.abs().max(1.0));
159 assert!((actual.im - expected.im).abs() <= 1e-15 * expected.im.abs().max(1.0));
160 }
161
162 #[test]
163 fn huge_denominator_stays_representable() {
164 let scale = 2f64.powi(600);
166 let b = c64(scale, scale);
167 let actual = robust_complex_divide_f64(c64(1.0, 0.0), b);
168 let expected = 2f64.powi(-601);
169 assert!(
170 (actual.re - expected).abs() <= expected * 1e-15,
171 "{actual:?}"
172 );
173 assert!(
174 (actual.im + expected).abs() <= expected * 1e-15,
175 "{actual:?}"
176 );
177 }
178
179 #[test]
180 fn tiny_denominator_stays_representable() {
181 let scale = 2f64.powi(-600);
183 let b = c64(scale, scale);
184 let actual = robust_complex_divide_f64(c64(1.0, 0.0), b);
185 let expected = 2f64.powi(599);
186 assert!(
187 (actual.re - expected).abs() <= expected * 1e-15,
188 "{actual:?}"
189 );
190 assert!(
191 (actual.im + expected).abs() <= expected * 1e-15,
192 "{actual:?}"
193 );
194 }
195
196 #[test]
197 fn infinite_components_produce_signed_zeros() {
198 let actual = robust_complex_divide_f64(c64(1.0, 2.0), c64(f64::INFINITY, 1.0));
199 assert_eq!(actual.re, 0.0);
200 assert!(
201 actual.im.is_sign_negative() && actual.im == 0.0,
202 "{actual:?}"
203 );
204 }
205
206 #[test]
207 fn f32_widens_so_the_squares_cannot_overflow() {
208 let scale = 2f32.powi(60);
209 let actual =
210 robust_complex_divide_f32(Complex32::new(1.0, 0.0), Complex32::new(scale, scale));
211 let expected = 2f32.powi(-61);
212 assert!(
213 (actual.re - expected).abs() <= expected * 1e-6,
214 "{actual:?}"
215 );
216 assert!(
217 (actual.im + expected).abs() <= expected * 1e-6,
218 "{actual:?}"
219 );
220 }
221}