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]
43pub fn mul(a: Cx, b: Cx) -> Cx {
44 [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]]
45}
46
47#[inline]
50pub fn div(a: Cx, b: Cx) -> Cx {
51 if b[0] == 0.0 && b[1] == 0.0 {
52 let step = |x: f64| if x == 0.0 { 0.0 } else { f64::INFINITY.copysign(x) };
53 return [step(a[0]), step(a[1])];
54 }
55 if b[0].abs() >= b[1].abs() {
57 let r = b[1] / b[0];
58 let d = b[0] + b[1] * r;
59 [(a[0] + a[1] * r) / d, (a[1] - a[0] * r) / d]
60 } else {
61 let r = b[0] / b[1];
62 let d = b[0] * r + b[1];
63 [(a[0] * r + a[1]) / d, (a[1] * r - a[0]) / d]
64 }
65}
66
67#[inline]
68pub fn abs(z: Cx) -> f64 {
69 z[0].hypot(z[1])
70}
71
72#[inline]
74pub fn arg(z: Cx) -> f64 {
75 z[1].atan2(z[0])
79}
80
81#[inline]
83pub fn signum(z: Cx) -> Cx {
84 let m = abs(z);
85 if m == 0.0 { ZERO } else { [z[0] / m, z[1] / m] }
86}
87
88#[inline]
89pub fn recip(z: Cx) -> Cx {
90 div(ONE, z)
91}
92
93#[inline]
96fn principal(z: Cx) -> Cx {
97 if z[1] == 0.0 { [z[0], 0.0] } else { z }
98}
99
100#[inline]
101pub fn exp(z: Cx) -> Cx {
102 let m = z[0].exp();
103 [m * z[1].cos(), m * z[1].sin()]
104}
105
106#[inline]
108pub fn ln(z: Cx) -> Cx {
109 let z = principal(z);
110 [abs(z).ln(), arg(z)]
111}
112
113#[inline]
117pub fn sqrt(z: Cx) -> Cx {
118 let z = principal(z);
119 if z[0] == 0.0 && z[1] == 0.0 {
120 return ZERO;
121 }
122 let t = ((abs(z) + z[0].abs()) / 2.0).sqrt();
123 if z[0] >= 0.0 {
124 [t, z[1] / (2.0 * t)]
125 } else {
126 [z[1].abs() / (2.0 * t), t.copysign(z[1])]
127 }
128}
129
130pub fn pow(a: Cx, b: Cx) -> Cx {
133 if b[1] == 0.0 && b[0].fract() == 0.0 && b[0].abs() <= 1024.0 {
134 let n = b[0] as i64;
135 if n == 0 {
136 return ONE;
137 }
138 let mut acc = ONE;
139 let mut base = if n < 0 { recip(a) } else { a };
140 let mut k = n.unsigned_abs();
141 while k > 0 {
142 if k & 1 == 1 {
143 acc = mul(acc, base);
144 }
145 base = mul(base, base);
146 k >>= 1;
147 }
148 return acc;
149 }
150 if a[0] == 0.0 && a[1] == 0.0 {
151 return if b[0] == 0.0 && b[1] == 0.0 { ONE } else { ZERO };
152 }
153 if a[1] == 0.0 && a[0] < 0.0 && b[1] == 0.0 {
157 let m = (-a[0]).powf(b[0]);
158 let (c, s) = cos_sin_pi(b[0]);
159 return [m * c, m * s];
160 }
161 exp(mul(b, ln(a)))
162}
163
164fn cos_sin_pi(t: f64) -> (f64, f64) {
166 let r = t.rem_euclid(2.0);
167 let half_turns = r * 2.0;
168 if half_turns.fract() == 0.0 {
169 return match half_turns as i64 {
170 0 => (1.0, 0.0),
171 1 => (0.0, 1.0),
172 2 => (-1.0, 0.0),
173 _ => (0.0, -1.0),
174 };
175 }
176 let angle = std::f64::consts::PI * r;
177 (angle.cos(), angle.sin())
178}
179
180#[inline]
182pub fn log(base: Cx, z: Cx) -> Cx {
183 div(ln(z), ln(base))
184}
185
186#[inline]
188pub fn root(x: Cx, y: Cx) -> Cx {
189 pow(y, recip(x))
190}
191
192pub fn floor(z: Cx) -> Cx {
196 let (bx, by) = (z[0].floor(), z[1].floor());
197 let (r, s) = (z[0] - bx, z[1] - by);
198 if r + s < 1.0 {
199 [bx, by]
200 } else if r >= s {
201 [bx + 1.0, by]
202 } else {
203 [bx, by + 1.0]
204 }
205}
206
207#[inline]
209pub fn ceil(z: Cx) -> Cx {
210 neg(floor(neg(z)))
211}
212
213#[inline]
215pub fn residue(x: Cx, y: Cx) -> Cx {
216 if x[0] == 0.0 && x[1] == 0.0 {
217 return y;
218 }
219 sub(y, mul(x, floor(div(y, x))))
220}
221
222pub fn gcd(a: Cx, b: Cx) -> Cx {
225 let (mut a, mut b) = (a, b);
226 for _ in 0..1024 {
229 if b[0] == 0.0 && b[1] == 0.0 {
230 return first_quadrant(a);
231 }
232 let q = div(a, b);
233 let rounded = [round_half_away(q[0]), round_half_away(q[1])];
234 let r = sub(a, mul(b, rounded));
235 if abs(r) >= abs(b) {
236 return first_quadrant(b);
237 }
238 a = b;
239 b = r;
240 }
241 first_quadrant(a)
242}
243
244fn first_quadrant(z: Cx) -> Cx {
248 let mut z = z;
249 for _ in 0..4 {
250 if z[0] > 0.0 && z[1] >= 0.0 {
251 return z;
252 }
253 if z[0] == 0.0 && z[1] == 0.0 {
254 return ZERO;
255 }
256 z = mul(I, z);
257 }
258 z
259}
260
261#[inline]
263pub fn lcm(a: Cx, b: Cx) -> Cx {
264 let g = gcd(a, b);
265 if g[0] == 0.0 && g[1] == 0.0 { ZERO } else { div(mul(a, b), g) }
266}
267
268fn round_half_away(x: f64) -> f64 {
269 if x < 0.0 { -(-x + 0.5).floor() } else { (x + 0.5).floor() }
270}
271
272#[inline]
275pub fn sin(z: Cx) -> Cx {
276 [z[0].sin() * z[1].cosh(), z[0].cos() * z[1].sinh()]
277}
278
279#[inline]
280pub fn cos(z: Cx) -> Cx {
281 [z[0].cos() * z[1].cosh(), -z[0].sin() * z[1].sinh()]
282}
283
284#[inline]
285pub fn tan(z: Cx) -> Cx {
286 div(sin(z), cos(z))
287}
288
289#[inline]
290pub fn sinh(z: Cx) -> Cx {
291 [z[0].sinh() * z[1].cos(), z[0].cosh() * z[1].sin()]
292}
293
294#[inline]
295pub fn cosh(z: Cx) -> Cx {
296 [z[0].cosh() * z[1].cos(), z[0].sinh() * z[1].sin()]
297}
298
299#[inline]
300pub fn tanh(z: Cx) -> Cx {
301 div(sinh(z), cosh(z))
302}
303
304pub fn asin(z: Cx) -> Cx {
306 let w = sqrt(sub(ONE, mul(z, z)));
307 mul([0.0, -1.0], ln(add(mul(I, z), w)))
308}
309
310pub fn acos(z: Cx) -> Cx {
312 sub([std::f64::consts::FRAC_PI_2, 0.0], asin(z))
313}
314
315pub fn atan(z: Cx) -> Cx {
318 let iz = mul(I, z);
319 mul([0.0, 0.5], sub(ln(sub(ONE, iz)), ln(add(ONE, iz))))
320}
321
322pub fn asinh(z: Cx) -> Cx {
324 ln(add(z, sqrt(add(mul(z, z), ONE))))
325}
326
327pub fn acosh(z: Cx) -> Cx {
329 mul(I, acos(z))
330}
331
332pub fn atanh(z: Cx) -> Cx {
334 mul([0.5, 0.0], sub(ln(add(ONE, z)), ln(sub(ONE, z))))
335}
336
337pub fn from_degrees(magnitude: f64, degrees: f64) -> Cx {
340 let turn = degrees.rem_euclid(360.0);
341 if turn % 90.0 == 0.0 {
342 let (c, s) = match (turn / 90.0) as i64 {
343 0 => (1.0, 0.0),
344 1 => (0.0, 1.0),
345 2 => (-1.0, 0.0),
346 _ => (0.0, -1.0),
347 };
348 return [magnitude * c, magnitude * s];
349 }
350 from_radians(magnitude, degrees * std::f64::consts::PI / 180.0)
351}
352
353#[inline]
355pub fn from_radians(magnitude: f64, radians: f64) -> Cx {
356 [magnitude * radians.cos(), magnitude * radians.sin()]
357}
358
359pub fn circle(k: i64, y: Cx) -> Option<Cx> {
362 let one_plus_sq = add(ONE, mul(y, y));
363 Some(match k {
364 0 => sqrt(sub(ONE, mul(y, y))),
365 1 => sin(y),
366 2 => cos(y),
367 3 => tan(y),
368 4 => sqrt(one_plus_sq),
369 5 => sinh(y),
370 6 => cosh(y),
371 7 => tanh(y),
372 8 => sqrt(neg(one_plus_sq)),
373 9 => from_real(y[0]),
374 10 => from_real(abs(y)),
375 11 => from_real(y[1]),
376 12 => from_real(arg(y)),
377 -1 => asin(y),
378 -2 => acos(y),
379 -3 => atan(y),
380 -4 => sqrt(sub(mul(y, y), ONE)),
381 -5 => asinh(y),
382 -6 => acosh(y),
383 -7 => atanh(y),
384 -8 => neg(sqrt(neg(one_plus_sq))),
385 -9 => y,
386 -10 => conj(y),
387 -11 => mul(I, y),
388 -12 => exp(mul(I, y)),
389 _ => return None,
390 })
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 fn close(a: Cx, b: Cx) -> bool {
398 (a[0] - b[0]).abs() < 1e-9 && (a[1] - b[1]).abs() < 1e-9
399 }
400
401 #[test]
402 fn multiplication_and_division_are_inverse() {
403 let a = [3.0, 4.0];
404 let b = [1.0, -2.0];
405 assert!(close(div(mul(a, b), b), a));
406 assert_eq!(mul([1.0, 2.0], [1.0, -2.0]), [5.0, 0.0]);
407 }
408
409 #[test]
410 fn dividing_by_zero_follows_the_real_rule_on_both_parts() {
411 assert_eq!(div(ZERO, ZERO), ZERO);
412 assert_eq!(div(ONE, ZERO), [f64::INFINITY, 0.0]);
413 assert_eq!(div(I, ZERO), [0.0, f64::INFINITY]);
414 }
415
416 #[test]
417 fn square_root_of_a_negative_real_takes_the_principal_branch() {
418 assert!(close(sqrt([-4.0, 0.0]), [0.0, 2.0]));
419 assert!(close(sqrt([-4.0, -0.0]), [0.0, 2.0]));
421 }
422
423 #[test]
424 fn an_integer_power_is_exact() {
425 assert_eq!(pow(I, [2.0, 0.0]), [-1.0, 0.0]);
426 assert_eq!(pow([3.0, 4.0], [2.0, 0.0]), [-7.0, 24.0]);
427 }
428
429 #[test]
430 fn complex_floor_keeps_the_residue_inside_the_unit_disc() {
431 assert_eq!(floor([3.0, 4.0]), [3.0, 4.0]);
432 assert_eq!(floor([0.6, 0.8]), [0.0, 1.0]);
433 assert_eq!(floor([3.5, 4.5]), [4.0, 4.0]);
434 assert!(close(residue([5.0, 0.0], [3.0, 4.0]), [3.0, -1.0]));
435 }
436
437 #[test]
438 fn gaussian_gcd_and_lcm() {
439 assert!(close(gcd([3.0, 4.0], [1.0, 2.0]), ONE));
440 assert!(close(lcm([3.0, 4.0], [1.0, 2.0]), [-5.0, 10.0]));
441 }
442}