Skip to main content

cinrs_rt/
complex.rs

1//! C's complex arithmetic, as the generated code calls it.
2//!
3//! # What C's operators actually do
4//!
5//! Two rules decide everything in this module, and neither is what a naive
6//! reading of "the usual arithmetic conversions" suggests.
7//!
8//! * **A real operand stays real.** C99 6.3.1.8 says the common type of a real
9//!   and a complex operand is the complex one, but the *result* it prescribes
10//!   is the same one the componentwise operation gives — and GCC and Clang
11//!   both compute it componentwise, which is observable: `3.0 * z` for
12//!   `z = (-0.0, -0.0)` is `(-0.0, -0.0)` componentwise and `(+0.0, -0.0)`
13//!   through the full product, because `3·(−0) − 0·(−0)` is `+0`. The one
14//!   exception is `real / complex`, which really is the full division with the
15//!   imaginary part of the numerator taken as zero. Each mixed form therefore
16//!   has a function of its own here: [`mul_real_f64`], [`add_real_f64`],
17//!   [`sub_real_f64`], [`real_sub_f64`], [`div_real_f64`] and
18//!   [`real_div_f64`], with the `f32` twins beside them.
19//!
20//! * **Infinities are recovered.** C99 Annex G.5.1 (which GCC implements by
21//!   default — `-fno-cx-limited-range`) says that a product or a quotient
22//!   involving an infinity must be infinite, even where the naive formula
23//!   produces NaN + iNaN out of an `∞ − ∞` or an `∞ · 0`. [`mul_f64`] and
24//!   [`div_f64`] therefore compute the cheap answer first and only fix it up
25//!   when both parts came out NaN, which is what makes the common case cost
26//!   four multiplications and an addition.
27//!
28//! # The algorithms
29//!
30//! The product — in both widths — is the schoolbook one, `(ac − bd) +
31//! i(ad + bc)`, followed by Annex G.5.1's recovery: an infinite operand is
32//! reduced to a signed one or zero, a NaN in the *other* operand is reduced to
33//! a signed zero, and the product is recomputed scaled by infinity so that the
34//! sign of each part survives.
35//!
36//! The quotient takes a different route in each width, because what is cheap
37//! and exact in one is not available in the other.
38//!
39//! * [`div_f32`] uses the **closed form in the wider format**:
40//!   `c² + d²` cannot overflow or underflow in `f64` for any `f32` operands, so
41//!   `(ac + bd)/(c² + d²)` and `(bc − ad)/(c² + d²)` are computed in `f64` and
42//!   rounded once.
43//! * [`div_f64`] has no wider format to hand, so it uses **Smith's
44//!   algorithm** — divide through by whichever of the divisor's parts is
45//!   larger, so that `c² + d²` cannot overflow when a single part can — with
46//!   Baudin and Smith's refinement for a *subnormal* ratio, where `a · (d/c)`
47//!   has already lost bits that the equal `(a/c) · d` still has.
48//!
49//! Both then apply the matching recovery: a zero divisor gives a signed
50//! infinity, an infinite dividend over a finite divisor gives an infinity, and
51//! a finite dividend over an infinite divisor gives a signed zero.
52//!
53//! All of it is written from the standard's description rather than taken from
54//! any implementation, and `tests/complex.rs` checks the result against the
55//! host's own C compiler: every combination of `0`, `−0`, `±1`, `±2`, `±10⁸`,
56//! `±10⁻⁸`, `±10¹⁵⁰`, `±10⁻¹⁵⁰`, `±∞` and NaN — a hundred and thirty thousand
57//! pairs — agrees exactly, and the product agrees for the `10³⁰⁰` and
58//! subnormal magnitudes too.
59//!
60//! # Where it is not the last word
61//!
62//! `libgcc` goes further on the `f64` quotient: it *prescales* both operands
63//! by powers of two before dividing, which buys accuracy in the last corner —
64//! operands whose exponents are hundreds apart, where an intermediate of
65//! Smith's algorithm underflows even after the refinement above. GCC's own
66//! `gcc.c-torture/execute/ieee/cdivchkd.c` is four such quotients, and two of
67//! its four are beyond what is here. C leaves the accuracy of complex
68//! arithmetic implementation-defined (Annex G.6), so this is a quality gap and
69//! not a conformance one; `doc/gcc-torture.md` records it.
70
71use crate::Complex;
72
73/// Generates the whole module for one floating format.
74///
75/// `$t` is the component type, `$suffix` the one the C library and this module
76/// spell the format with, and the two `$doc` fragments name it in the
77/// documentation.
78macro_rules! complex_ops {
79    ($t:ty, $mul:ident, $div:ident, $mul_real:ident, $real_mul:ident, $add_real:ident,
80     $real_add:ident, $sub_real:ident, $real_sub:ident, $div_real:ident, $real_div:ident,
81     $conj:ident, $proj:ident, $nonzero:ident, $eq:ident, $ne:ident, $cty:literal) => {
82        /// The product of two
83        #[doc = $cty]
84        /// values (C99 6.5.5, Annex G.5.1).
85        ///
86        /// The naive product, with the infinities Annex G.5.1 requires
87        /// recovered when it comes out NaN + iNaN.
88        #[inline]
89        pub fn $mul(z: Complex<$t>, w: Complex<$t>) -> Complex<$t> {
90            let (mut a, mut b, mut c, mut d) = (z.re, z.im, w.re, w.im);
91            let (ac, bd, ad, bc) = (a * c, b * d, a * d, b * c);
92            let mut x = ac - bd;
93            let mut y = ad + bc;
94            if x.is_nan() && y.is_nan() {
95                let mut recalc = false;
96                // An infinite operand: reduce it to a signed one or zero, and
97                // reduce a NaN in the other operand to a signed zero, so that
98                // the recomputation below cannot produce another NaN.
99                if a.is_infinite() || b.is_infinite() {
100                    a = unit(a);
101                    b = unit(b);
102                    c = tame(c);
103                    d = tame(d);
104                    recalc = true;
105                }
106                if c.is_infinite() || d.is_infinite() {
107                    c = unit(c);
108                    d = unit(d);
109                    a = tame(a);
110                    b = tame(b);
111                    recalc = true;
112                }
113                // Neither operand is infinite, but one of the four products
114                // overflowed to one: the result is infinite all the same.
115                if !recalc
116                    && (ac.is_infinite()
117                        || bd.is_infinite()
118                        || ad.is_infinite()
119                        || bc.is_infinite())
120                {
121                    a = tame(a);
122                    b = tame(b);
123                    c = tame(c);
124                    d = tame(d);
125                    recalc = true;
126                }
127                if recalc {
128                    let inf = <$t>::INFINITY;
129                    x = inf * (a * c - b * d);
130                    y = inf * (a * d + b * c);
131                }
132            }
133            Complex { re: x, im: y }
134        }
135
136        /// A
137        #[doc = $cty]
138        /// value times a real one, which C computes componentwise.
139        #[inline]
140        pub fn $mul_real(z: Complex<$t>, x: $t) -> Complex<$t> {
141            Complex {
142                re: z.re * x,
143                im: z.im * x,
144            }
145        }
146
147        /// A real value times a
148        #[doc = $cty]
149        /// one: the same product, with the operands in source order so that
150        /// the generated code evaluates them where they were written.
151        #[inline]
152        pub fn $real_mul(x: $t, z: Complex<$t>) -> Complex<$t> {
153            $mul_real(z, x)
154        }
155
156        /// A
157        #[doc = $cty]
158        /// value plus a real one, which touches the real part only.
159        #[inline]
160        pub fn $add_real(z: Complex<$t>, x: $t) -> Complex<$t> {
161            Complex {
162                re: z.re + x,
163                im: z.im,
164            }
165        }
166
167        /// A real value plus a
168        #[doc = $cty]
169        /// one; see [`
170        #[doc = stringify!($real_mul)]
171        /// `] for why the order matters.
172        #[inline]
173        pub fn $real_add(x: $t, z: Complex<$t>) -> Complex<$t> {
174            Complex {
175                re: x + z.re,
176                im: z.im,
177            }
178        }
179
180        /// A
181        #[doc = $cty]
182        /// value minus a real one, which touches the real part only.
183        #[inline]
184        pub fn $sub_real(z: Complex<$t>, x: $t) -> Complex<$t> {
185            Complex {
186                re: z.re - x,
187                im: z.im,
188            }
189        }
190
191        /// A real value minus a
192        #[doc = $cty]
193        /// one, which negates the imaginary part.
194        #[inline]
195        pub fn $real_sub(x: $t, z: Complex<$t>) -> Complex<$t> {
196            Complex {
197                re: x - z.re,
198                im: -z.im,
199            }
200        }
201
202        /// A
203        #[doc = $cty]
204        /// value divided by a real one, which C computes componentwise.
205        #[inline]
206        pub fn $div_real(z: Complex<$t>, x: $t) -> Complex<$t> {
207            Complex {
208                re: z.re / x,
209                im: z.im / x,
210            }
211        }
212
213        /// A real value divided by a
214        #[doc = $cty]
215        /// one.
216        ///
217        /// The one mixed form that is *not* componentwise: it is the full
218        /// complex division with a zero imaginary part in the numerator, which
219        /// is what GCC and Clang emit.
220        #[inline]
221        pub fn $real_div(x: $t, z: Complex<$t>) -> Complex<$t> {
222            $div(Complex { re: x, im: 0 as $t }, z)
223        }
224
225        /// The conjugate of a
226        #[doc = $cty]
227        /// value: `conj`, and GNU's `~z`.
228        #[inline]
229        pub fn $conj(z: Complex<$t>) -> Complex<$t> {
230            Complex {
231                re: z.re,
232                im: -z.im,
233            }
234        }
235
236        /// The projection of a
237        #[doc = $cty]
238        /// value onto the Riemann sphere: C99 7.3.9.5's `cproj`.
239        ///
240        /// Everything is itself except a value with an infinite part, which
241        /// becomes `+∞` with the imaginary part's sign kept on a zero.
242        #[inline]
243        pub fn $proj(z: Complex<$t>) -> Complex<$t> {
244            if z.re.is_infinite() || z.im.is_infinite() {
245                Complex {
246                    re: <$t>::INFINITY,
247                    im: copysign(0 as $t, z.im),
248                }
249            } else {
250                z
251            }
252        }
253
254        /// Whether a
255        #[doc = $cty]
256        /// value is non-zero, which is what C's conversion to `_Bool` and a
257        /// controlling expression ask (C99 6.3.1.2).
258        ///
259        /// Either part being non-zero is enough, so a NaN counts as true.
260        #[inline]
261        pub fn $nonzero(z: Complex<$t>) -> bool {
262            z.re != 0 as $t || z.im != 0 as $t
263        }
264
265        /// Whether two
266        #[doc = $cty]
267        /// values are equal: C99 6.5.9p3, both parts equal.
268        ///
269        /// The same answer `PartialEq` gives, and it is here because that one
270        /// takes `&self`: a reference to a field of a `#[repr(packed)]`
271        /// record is `E0793`, and a packed complex member is exactly what
272        /// `gcc.c-torture/execute/20020227-1` compares. Taking both operands
273        /// by value asks for a copy, which a packed field will give.
274        #[inline]
275        pub fn $eq(z: Complex<$t>, w: Complex<$t>) -> bool {
276            z.re == w.re && z.im == w.im
277        }
278
279        /// Whether two
280        #[doc = $cty]
281        /// values differ — the negation of
282        #[doc = concat!("[`", stringify!($eq), "`]")]
283        /// , and `!=` in C.
284        #[inline]
285        pub fn $ne(z: Complex<$t>, w: Complex<$t>) -> bool {
286            !$eq(z, w)
287        }
288    };
289}
290
291complex_ops!(
292    f32,
293    mul_f32,
294    div_f32,
295    mul_real_f32,
296    real_mul_f32,
297    add_real_f32,
298    real_add_f32,
299    sub_real_f32,
300    real_sub_f32,
301    div_real_f32,
302    real_div_f32,
303    conj_f32,
304    proj_f32,
305    nonzero_f32,
306    eq_f32,
307    ne_f32,
308    "`float _Complex`"
309);
310
311complex_ops!(
312    f64,
313    mul_f64,
314    div_f64,
315    mul_real_f64,
316    real_mul_f64,
317    add_real_f64,
318    real_add_f64,
319    sub_real_f64,
320    real_sub_f64,
321    div_real_f64,
322    real_div_f64,
323    conj_f64,
324    proj_f64,
325    nonzero_f64,
326    eq_f64,
327    ne_f64,
328    "`double _Complex`"
329);
330
331/// The quotient of two `float _Complex` values (C99 6.5.5, Annex G.5.1).
332///
333/// The closed form, computed in `f64`: `c² + d²` is exact enough and can
334/// neither overflow nor underflow there for any `f32` operands, so no scaling
335/// is needed and the result is rounded exactly once.
336#[inline]
337pub fn div_f32(z: Complex<f32>, w: Complex<f32>) -> Complex<f32> {
338    let (a, b) = (f64::from(z.re), f64::from(z.im));
339    let (c, d) = (f64::from(w.re), f64::from(w.im));
340    let denom = c * c + d * d;
341    let (x, y) = recover_quotient(a, b, c, d, (a * c + b * d) / denom, (b * c - a * d) / denom);
342    Complex {
343        re: x as f32,
344        im: y as f32,
345    }
346}
347
348/// The quotient of two `double _Complex` values (C99 6.5.5, Annex G.5.1).
349///
350/// Smith's algorithm: there is no wider format to compute `c² + d²` in, so the
351/// division is carried out through the ratio of the divisor's two parts, which
352/// keeps the denominator in range whenever the quotient itself is.
353///
354/// The `ratio.abs() > f64::MIN_POSITIVE` test is Smith's algorithm's one weak
355/// spot, closed the way Baudin and Smith describe: when the ratio is
356/// *subnormal* it has already lost bits, so multiplying by it throws away
357/// precision the operands still had. `a · (d/c)` and `(a/c) · d` are the same
358/// number, and the second one keeps it, so the second one is used exactly
359/// where the first would not do.
360#[inline]
361pub fn div_f64(z: Complex<f64>, w: Complex<f64>) -> Complex<f64> {
362    let (a, b, c, d) = (z.re, z.im, w.re, w.im);
363    let (x, y) = if abs(c) < abs(d) {
364        let ratio = c / d;
365        let denom = c * ratio + d;
366        if abs(ratio) > f64::MIN_POSITIVE {
367            ((a * ratio + b) / denom, (b * ratio - a) / denom)
368        } else {
369            (((a / d) * c + b) / denom, ((b / d) * c - a) / denom)
370        }
371    } else {
372        let ratio = d / c;
373        let denom = d * ratio + c;
374        if abs(ratio) > f64::MIN_POSITIVE {
375            ((b * ratio + a) / denom, (b - a * ratio) / denom)
376        } else {
377            (((b / c) * d + a) / denom, (b - (a / c) * d) / denom)
378        }
379    };
380    let (re, im) = recover_quotient(a, b, c, d, x, y);
381    Complex { re, im }
382}
383
384/// Annex G.5.1's recovery for a quotient that came out NaN + iNaN.
385///
386/// Shared by both widths: [`div_f32`] has already widened its operands, so the
387/// three cases — a zero divisor, an infinite dividend, an infinite divisor —
388/// are the same arithmetic in both.
389#[inline]
390fn recover_quotient(mut a: f64, mut b: f64, mut c: f64, mut d: f64, x: f64, y: f64) -> (f64, f64) {
391    if !(x.is_nan() && y.is_nan()) {
392        return (x, y);
393    }
394    let inf = f64::INFINITY;
395    if c == 0.0 && d == 0.0 && (!a.is_nan() || !b.is_nan()) {
396        // Division by zero: a signed infinity, not a NaN.
397        let scale = copysign(inf, c);
398        return (scale * a, scale * b);
399    }
400    if (a.is_infinite() || b.is_infinite()) && c.is_finite() && d.is_finite() {
401        a = unit(a);
402        b = unit(b);
403        return (inf * (a * c + b * d), inf * (b * c - a * d));
404    }
405    if (c.is_infinite() || d.is_infinite()) && a.is_finite() && b.is_finite() {
406        c = unit(c);
407        d = unit(d);
408        return (0.0 * (a * c + b * d), 0.0 * (b * c - a * d));
409    }
410    (x, y)
411}
412
413/// `float _Complex` widened to `double _Complex`.
414#[inline]
415pub fn widen_f32(z: Complex<f32>) -> Complex<f64> {
416    Complex {
417        re: f64::from(z.re),
418        im: f64::from(z.im),
419    }
420}
421
422/// `double _Complex` narrowed to `float _Complex`.
423#[inline]
424pub fn narrow_f64(z: Complex<f64>) -> Complex<f32> {
425    Complex {
426        re: z.re as f32,
427        im: z.im as f32,
428    }
429}
430
431/// The magnitude of a float, without `core::f32::abs` — which this crate does
432/// not need a `libm` for.
433trait Bits: Copy {
434    /// `|x|`.
435    fn magnitude(self) -> Self;
436    /// `x` with the sign of `y`.
437    fn with_sign(self, y: Self) -> Self;
438}
439
440macro_rules! bits {
441    ($t:ty, $u:ty) => {
442        impl Bits for $t {
443            #[inline]
444            fn magnitude(self) -> Self {
445                const SIGN: $u = 1 << (<$u>::BITS - 1);
446                <$t>::from_bits(self.to_bits() & !SIGN)
447            }
448            #[inline]
449            fn with_sign(self, y: Self) -> Self {
450                const SIGN: $u = 1 << (<$u>::BITS - 1);
451                <$t>::from_bits((self.to_bits() & !SIGN) | (y.to_bits() & SIGN))
452            }
453        }
454    };
455}
456
457bits!(f32, u32);
458bits!(f64, u64);
459
460/// `|x|`, on either width.
461#[inline]
462fn abs<T: Bits>(x: T) -> T {
463    x.magnitude()
464}
465
466/// `x` with the sign bit of `y`, on either width.
467#[inline]
468fn copysign<T: Bits>(x: T, y: T) -> T {
469    x.with_sign(y)
470}
471
472/// One or zero, with `x`'s sign: what Annex G.5.1 reduces an operand to before
473/// recomputing an overflowed product or quotient.
474#[inline]
475fn unit<T: Bits + Float>(x: T) -> T {
476    copysign(if x.is_infinite() { T::ONE } else { T::ZERO }, x)
477}
478
479/// A NaN reduced to a signed zero, and everything else left alone: the other
480/// half of Annex G.5.1's recovery.
481#[inline]
482fn tame<T: Bits + Float>(x: T) -> T {
483    if x.is_nan() { copysign(T::ZERO, x) } else { x }
484}
485
486/// The handful of predicates [`unit`] and [`tame`] need on both widths.
487trait Float: Copy {
488    /// `0`.
489    const ZERO: Self;
490    /// `1`.
491    const ONE: Self;
492    /// Whether this is an infinity.
493    fn is_infinite(self) -> bool;
494    /// Whether this is a NaN.
495    fn is_nan(self) -> bool;
496}
497
498macro_rules! float {
499    ($t:ty) => {
500        impl Float for $t {
501            const ZERO: Self = 0.0;
502            const ONE: Self = 1.0;
503            #[inline]
504            fn is_infinite(self) -> bool {
505                <$t>::is_infinite(self)
506            }
507            #[inline]
508            fn is_nan(self) -> bool {
509                <$t>::is_nan(self)
510            }
511        }
512    };
513}
514
515float!(f32);
516float!(f64);
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521
522    fn same(a: f64, b: f64) -> bool {
523        if a.is_nan() && b.is_nan() {
524            return true;
525        }
526        a == b && a.is_sign_negative() == b.is_sign_negative()
527    }
528
529    fn same_c(a: Complex<f64>, b: Complex<f64>) -> bool {
530        same(a.re, b.re) && same(a.im, b.im)
531    }
532
533    #[test]
534    fn the_ordinary_product_and_quotient_are_the_school_ones() {
535        let z = Complex::new(1.0, 2.0);
536        let w = Complex::new(3.0, -4.0);
537        assert_eq!(mul_f64(z, w), Complex::new(11.0, 2.0));
538        assert_eq!(div_f64(z, w), Complex::new(-0.2, 0.4));
539    }
540
541    #[test]
542    fn an_infinity_survives_a_naive_nan() {
543        let inf = f64::INFINITY;
544        // (∞, 0) · (3, −4) is (∞·3 − 0·(−4), ∞·(−4) + 0·3) = (∞, −∞) only
545        // after the recovery: the naive imaginary part is −∞ + NaN.
546        assert!(same_c(
547            mul_f64(Complex::new(inf, 0.0), Complex::new(3.0, -4.0)),
548            Complex::new(inf, -inf)
549        ));
550        assert!(same_c(
551            div_f64(Complex::new(inf, 0.0), Complex::new(3.0, -4.0)),
552            Complex::new(inf, inf)
553        ));
554        // A finite value over an infinite one is a signed zero.
555        assert!(same_c(
556            div_f64(Complex::new(3.0, -4.0), Complex::new(inf, 0.0)),
557            Complex::new(0.0, -0.0)
558        ));
559    }
560
561    #[test]
562    fn division_by_zero_is_a_signed_infinity() {
563        let inf = f64::INFINITY;
564        assert!(same_c(
565            div_f64(Complex::new(1.0, 2.0), Complex::new(0.0, 0.0)),
566            Complex::new(inf, inf)
567        ));
568        assert!(same_c(
569            div_f64(Complex::new(1.0, 2.0), Complex::new(-0.0, -0.0)),
570            Complex::new(-inf, -inf)
571        ));
572    }
573
574    #[test]
575    fn a_real_operand_is_componentwise() {
576        let nzero = Complex::new(-0.0, -0.0);
577        // The full product would give (+0, −0) here; C gives (−0, −0).
578        assert!(same_c(mul_real_f64(nzero, 3.0), Complex::new(-0.0, -0.0)));
579        assert!(same_c(
580            real_sub_f64(0.0, Complex::new(1.0, 0.0)),
581            Complex::new(-1.0, -0.0)
582        ));
583        assert!(same_c(
584            div_real_f64(Complex::new(1.0, 2.0), 0.0),
585            Complex::new(f64::INFINITY, f64::INFINITY)
586        ));
587    }
588
589    #[test]
590    fn conjugation_projection_and_truth() {
591        let z = Complex::new(1.0, 2.0);
592        assert_eq!(conj_f64(z), Complex::new(1.0, -2.0));
593        assert_eq!(proj_f64(z), z);
594        assert!(same_c(
595            proj_f64(Complex::new(1.0, f64::NEG_INFINITY)),
596            Complex::new(f64::INFINITY, -0.0)
597        ));
598        assert!(nonzero_f64(Complex::new(0.0, 1.0)));
599        assert!(!nonzero_f64(Complex::new(0.0, -0.0)));
600        assert!(nonzero_f64(Complex::new(f64::NAN, 0.0)));
601    }
602
603    #[test]
604    fn the_f32_forms_agree_with_the_f64_ones_on_exact_values() {
605        let z = Complex::new(1.0f32, 2.0);
606        let w = Complex::new(3.0f32, -4.0);
607        assert_eq!(mul_f32(z, w), Complex::new(11.0, 2.0));
608        assert_eq!(widen_f32(mul_f32(z, w)), Complex::new(11.0f64, 2.0));
609        assert_eq!(
610            narrow_f64(Complex::new(11.0f64, 2.0)),
611            Complex::new(11.0f32, 2.0)
612        );
613    }
614}