1pub type Cx = [f64; 2];
12
13pub const ZERO: Cx = [0.0, 0.0];
14pub const ONE: Cx = [1.0, 0.0];
15pub const I: Cx = [0.0, 1.0];
16
17#[inline]
18pub fn from_real(x: f64) -> Cx {
19 [x, 0.0]
20}
21
22#[inline]
23pub fn add(a: Cx, b: Cx) -> Cx {
24 [a[0] + b[0], a[1] + b[1]]
25}
26
27#[inline]
28pub fn sub(a: Cx, b: Cx) -> Cx {
29 [a[0] - b[0], a[1] - b[1]]
30}
31
32#[inline]
33pub fn neg(a: Cx) -> Cx {
34 [-a[0], -a[1]]
35}
36
37#[inline]
38pub fn conj(a: Cx) -> Cx {
39 [a[0], -a[1]]
40}
41
42#[inline]
49fn prod(x: f64, y: f64) -> f64 {
50 if (x == 0.0 || y == 0.0) && !(x.is_finite() && y.is_finite()) {
51 return 0.0;
52 }
53 x * y
54}
55
56#[inline]
57pub fn mul(a: Cx, b: Cx) -> Cx {
58 [prod(a[0], b[0]) - prod(a[1], b[1]), prod(a[0], b[1]) + prod(a[1], b[0])]
59}
60
61#[inline]
64pub fn div(a: Cx, b: Cx) -> Cx {
65 if b[0] == 0.0 && b[1] == 0.0 {
66 let step = |x: f64| if x == 0.0 { 0.0 } else { f64::INFINITY.copysign(x) };
67 return [step(a[0]), step(a[1])];
68 }
69 if b[0].abs() >= b[1].abs() {
71 let r = b[1] / b[0];
72 let d = b[0] + b[1] * r;
73 [(a[0] + a[1] * r) / d, (a[1] - a[0] * r) / d]
74 } else {
75 let r = b[0] / b[1];
76 let d = b[0] * r + b[1];
77 [(a[0] * r + a[1]) / d, (a[1] * r - a[0]) / d]
78 }
79}
80
81#[inline]
82pub fn abs(z: Cx) -> f64 {
83 z[0].hypot(z[1])
84}
85
86#[inline]
88pub fn arg(z: Cx) -> f64 {
89 z[1].atan2(z[0])
93}
94
95#[inline]
97pub fn signum(z: Cx) -> Cx {
98 let m = abs(z);
99 if m == 0.0 { ZERO } else { [z[0] / m, z[1] / m] }
100}
101
102#[inline]
103pub fn recip(z: Cx) -> Cx {
104 div(ONE, z)
105}
106
107#[inline]
110fn principal(z: Cx) -> Cx {
111 if z[1] == 0.0 { [z[0], 0.0] } else { z }
112}
113
114#[inline]
115pub fn exp(z: Cx) -> Cx {
116 let m = z[0].exp();
117 [m * z[1].cos(), m * z[1].sin()]
118}
119
120#[inline]
122pub fn ln(z: Cx) -> Cx {
123 let z = principal(z);
124 [abs(z).ln(), arg(z)]
125}
126
127#[inline]
131pub fn sqrt(z: Cx) -> Cx {
132 let z = principal(z);
133 if z[0] == 0.0 && z[1] == 0.0 {
134 return ZERO;
135 }
136 let t = ((abs(z) + z[0].abs()) / 2.0).sqrt();
137 if z[0] >= 0.0 {
138 [t, z[1] / (2.0 * t)]
139 } else {
140 [z[1].abs() / (2.0 * t), t.copysign(z[1])]
141 }
142}
143
144pub fn pow(a: Cx, b: Cx) -> Cx {
147 if b[1] == 0.0 && b[0].fract() == 0.0 && b[0].abs() <= 1024.0 {
148 let n = b[0] as i64;
149 if n == 0 {
150 return ONE;
151 }
152 let mut acc = ONE;
153 let mut base = if n < 0 { recip(a) } else { a };
154 let mut k = n.unsigned_abs();
155 while k > 0 {
156 if k & 1 == 1 {
157 acc = mul(acc, base);
158 }
159 base = mul(base, base);
160 k >>= 1;
161 }
162 return acc;
163 }
164 if a[0] == 0.0 && a[1] == 0.0 {
165 return if b[0] == 0.0 && b[1] == 0.0 { ONE } else { ZERO };
166 }
167 if a[1] == 0.0 && a[0] < 0.0 && b[1] == 0.0 {
171 let m = (-a[0]).powf(b[0]);
172 let (c, s) = cos_sin_pi(b[0]);
173 return [prod(m, c), prod(m, s)];
177 }
178 exp(mul(b, ln(a)))
179}
180
181fn cos_sin_pi(t: f64) -> (f64, f64) {
183 let r = t.rem_euclid(2.0);
184 let half_turns = r * 2.0;
185 if half_turns.fract() == 0.0 {
186 return match half_turns as i64 {
187 0 => (1.0, 0.0),
188 1 => (0.0, 1.0),
189 2 => (-1.0, 0.0),
190 _ => (0.0, -1.0),
191 };
192 }
193 let angle = std::f64::consts::PI * r;
194 (angle.cos(), angle.sin())
195}
196
197#[inline]
199pub fn log(base: Cx, z: Cx) -> Cx {
200 div(ln(z), ln(base))
201}
202
203#[inline]
205pub fn root(x: Cx, y: Cx) -> Cx {
206 pow(y, recip(x))
207}
208
209pub fn floor(z: Cx) -> Cx {
213 let (bx, by) = (z[0].floor(), z[1].floor());
214 let (r, s) = (z[0] - bx, z[1] - by);
215 if r + s < 1.0 {
216 [bx, by]
217 } else if r >= s {
218 [bx + 1.0, by]
219 } else {
220 [bx, by + 1.0]
221 }
222}
223
224#[inline]
226pub fn ceil(z: Cx) -> Cx {
227 neg(floor(neg(z)))
228}
229
230#[inline]
232pub fn residue(x: Cx, y: Cx) -> Cx {
233 if x[0] == 0.0 && x[1] == 0.0 {
234 return y;
235 }
236 sub(y, mul(x, floor(div(y, x))))
237}
238
239pub fn gcd(a: Cx, b: Cx) -> Cx {
242 let (mut a, mut b) = (a, b);
243 for _ in 0..1024 {
246 if b[0] == 0.0 && b[1] == 0.0 {
247 return first_quadrant(a);
248 }
249 let q = div(a, b);
250 let rounded = [round_half_away(q[0]), round_half_away(q[1])];
251 let r = sub(a, mul(b, rounded));
252 if abs(r) >= abs(b) {
253 return first_quadrant(b);
254 }
255 a = b;
256 b = r;
257 }
258 first_quadrant(a)
259}
260
261fn first_quadrant(z: Cx) -> Cx {
265 let mut z = z;
266 for _ in 0..4 {
267 if z[0] > 0.0 && z[1] >= 0.0 {
268 return z;
269 }
270 if z[0] == 0.0 && z[1] == 0.0 {
271 return ZERO;
272 }
273 z = mul(I, z);
274 }
275 z
276}
277
278#[inline]
280pub fn lcm(a: Cx, b: Cx) -> Cx {
281 let g = gcd(a, b);
282 if g[0] == 0.0 && g[1] == 0.0 { ZERO } else { div(mul(a, b), g) }
283}
284
285fn round_half_away(x: f64) -> f64 {
286 if x < 0.0 { -(-x + 0.5).floor() } else { (x + 0.5).floor() }
287}
288
289#[inline]
292pub fn sin(z: Cx) -> Cx {
293 [z[0].sin() * z[1].cosh(), z[0].cos() * z[1].sinh()]
294}
295
296#[inline]
297pub fn cos(z: Cx) -> Cx {
298 [z[0].cos() * z[1].cosh(), -z[0].sin() * z[1].sinh()]
299}
300
301#[inline]
302pub fn tan(z: Cx) -> Cx {
303 div(sin(z), cos(z))
304}
305
306#[inline]
307pub fn sinh(z: Cx) -> Cx {
308 [z[0].sinh() * z[1].cos(), z[0].cosh() * z[1].sin()]
309}
310
311#[inline]
312pub fn cosh(z: Cx) -> Cx {
313 [z[0].cosh() * z[1].cos(), z[0].sinh() * z[1].sin()]
314}
315
316#[inline]
317pub fn tanh(z: Cx) -> Cx {
318 div(sinh(z), cosh(z))
319}
320
321pub fn asin(z: Cx) -> Cx {
323 let w = sqrt(sub(ONE, mul(z, z)));
324 mul([0.0, -1.0], ln(add(mul(I, z), w)))
325}
326
327pub fn acos(z: Cx) -> Cx {
329 sub([std::f64::consts::FRAC_PI_2, 0.0], asin(z))
330}
331
332pub fn atan(z: Cx) -> Cx {
335 let iz = mul(I, z);
336 mul([0.0, 0.5], sub(ln(sub(ONE, iz)), ln(add(ONE, iz))))
337}
338
339pub fn asinh(z: Cx) -> Cx {
341 ln(add(z, sqrt(add(mul(z, z), ONE))))
342}
343
344pub fn acosh(z: Cx) -> Cx {
346 mul(I, acos(z))
347}
348
349pub fn atanh(z: Cx) -> Cx {
351 mul([0.5, 0.0], sub(ln(add(ONE, z)), ln(sub(ONE, z))))
352}
353
354pub fn from_degrees(magnitude: f64, degrees: f64) -> Cx {
357 let turn = degrees.rem_euclid(360.0);
358 if turn % 90.0 == 0.0 {
359 let (c, s) = match (turn / 90.0) as i64 {
360 0 => (1.0, 0.0),
361 1 => (0.0, 1.0),
362 2 => (-1.0, 0.0),
363 _ => (0.0, -1.0),
364 };
365 return [magnitude * c, magnitude * s];
366 }
367 from_radians(magnitude, degrees * std::f64::consts::PI / 180.0)
368}
369
370#[inline]
372pub fn from_radians(magnitude: f64, radians: f64) -> Cx {
373 [magnitude * radians.cos(), magnitude * radians.sin()]
374}
375
376pub fn circle(k: i64, y: Cx) -> Option<Cx> {
379 let one_plus_sq = add(ONE, mul(y, y));
380 Some(match k {
381 0 => sqrt(sub(ONE, mul(y, y))),
382 1 => sin(y),
383 2 => cos(y),
384 3 => tan(y),
385 4 => sqrt(one_plus_sq),
386 5 => sinh(y),
387 6 => cosh(y),
388 7 => tanh(y),
389 8 => sqrt(neg(one_plus_sq)),
390 9 => from_real(y[0]),
391 10 => from_real(abs(y)),
392 11 => from_real(y[1]),
393 12 => from_real(arg(y)),
394 -1 => asin(y),
395 -2 => acos(y),
396 -3 => atan(y),
397 -4 => sqrt(sub(mul(y, y), ONE)),
398 -5 => asinh(y),
399 -6 => acosh(y),
400 -7 => atanh(y),
401 -8 => neg(sqrt(neg(one_plus_sq))),
402 -9 => y,
403 -10 => conj(y),
404 -11 => mul(I, y),
405 -12 => exp(mul(I, y)),
406 _ => return None,
407 })
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 fn close(a: Cx, b: Cx) -> bool {
415 (a[0] - b[0]).abs() < 1e-9 && (a[1] - b[1]).abs() < 1e-9
416 }
417
418 #[test]
419 fn multiplication_and_division_are_inverse() {
420 let a = [3.0, 4.0];
421 let b = [1.0, -2.0];
422 assert!(close(div(mul(a, b), b), a));
423 assert_eq!(mul([1.0, 2.0], [1.0, -2.0]), [5.0, 0.0]);
424 }
425
426 #[test]
427 fn dividing_by_zero_follows_the_real_rule_on_both_parts() {
428 assert_eq!(div(ZERO, ZERO), ZERO);
429 assert_eq!(div(ONE, ZERO), [f64::INFINITY, 0.0]);
430 assert_eq!(div(I, ZERO), [0.0, f64::INFINITY]);
431 }
432
433 #[test]
434 fn square_root_of_a_negative_real_takes_the_principal_branch() {
435 assert!(close(sqrt([-4.0, 0.0]), [0.0, 2.0]));
436 assert!(close(sqrt([-4.0, -0.0]), [0.0, 2.0]));
438 }
439
440 #[test]
441 fn an_integer_power_is_exact() {
442 assert_eq!(pow(I, [2.0, 0.0]), [-1.0, 0.0]);
443 assert_eq!(pow([3.0, 4.0], [2.0, 0.0]), [-7.0, 24.0]);
444 }
445
446 #[test]
447 fn complex_floor_keeps_the_residue_inside_the_unit_disc() {
448 assert_eq!(floor([3.0, 4.0]), [3.0, 4.0]);
449 assert_eq!(floor([0.6, 0.8]), [0.0, 1.0]);
450 assert_eq!(floor([3.5, 4.5]), [4.0, 4.0]);
451 assert!(close(residue([5.0, 0.0], [3.0, 4.0]), [3.0, -1.0]));
452 }
453
454 #[test]
455 fn gaussian_gcd_and_lcm() {
456 assert!(close(gcd([3.0, 4.0], [1.0, 2.0]), ONE));
457 assert!(close(lcm([3.0, 4.0], [1.0, 2.0]), [-5.0, 10.0]));
458 }
459}