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