Expand description
C’s complex arithmetic, as the generated code calls it.
§What C’s operators actually do
Two rules decide everything in this module, and neither is what a naive reading of “the usual arithmetic conversions” suggests.
-
A real operand stays real. C99 6.3.1.8 says the common type of a real and a complex operand is the complex one, but the result it prescribes is the same one the componentwise operation gives — and GCC and Clang both compute it componentwise, which is observable:
3.0 * zforz = (-0.0, -0.0)is(-0.0, -0.0)componentwise and(+0.0, -0.0)through the full product, because3·(−0) − 0·(−0)is+0. The one exception isreal / complex, which really is the full division with the imaginary part of the numerator taken as zero. Each mixed form therefore has a function of its own here:mul_real_f64,add_real_f64,sub_real_f64,real_sub_f64,div_real_f64andreal_div_f64, with thef32twins beside them. -
Infinities are recovered. C99 Annex G.5.1 (which GCC implements by default —
-fno-cx-limited-range) says that a product or a quotient involving an infinity must be infinite, even where the naive formula produces NaN + iNaN out of an∞ − ∞or an∞ · 0.mul_f64anddiv_f64therefore compute the cheap answer first and only fix it up when both parts came out NaN, which is what makes the common case cost four multiplications and an addition. Only that answer and the NaN test are inlined into the caller, as GCC and Clang inline the product before their call of__muldc3: the fix-up is a separate#[cold]function, never inlined, in both operations. Inlined, it made LLVM pack a loop’s real and imaginary parts into one vector for the sake of the path never taken and put the shuffles on the one that is, costing az = z * z + cloop a quarter of its time.
§The algorithms
The product — in both widths — is the schoolbook one, (ac − bd) + i(ad + bc), followed by Annex G.5.1’s recovery: an infinite operand is
reduced to a signed one or zero, a NaN in the other operand is reduced to
a signed zero, and the product is recomputed scaled by infinity so that the
sign of each part survives.
The quotient takes a different route in each width, because what is cheap and exact in one is not available in the other.
div_f32uses the closed form in the wider format:c² + d²cannot overflow or underflow inf64for anyf32operands, so(ac + bd)/(c² + d²)and(bc − ad)/(c² + d²)are computed inf64and rounded once.div_f64has no wider format to hand, so it uses Smith’s algorithm — divide through by whichever of the divisor’s parts is larger, so thatc² + d²cannot overflow when a single part can — with Baudin and Smith’s refinement for a subnormal ratio, wherea · (d/c)has already lost bits that the equal(a/c) · dstill has.
Both then apply the matching recovery: a zero divisor gives a signed infinity, an infinite dividend over a finite divisor gives an infinity, and a finite dividend over an infinite divisor gives a signed zero.
All of it is written from the standard’s description rather than taken from
any implementation, and tests/complex.rs checks the result against the
host’s own C compiler: every combination of 0, −0, ±1, ±2, ±10⁸,
±10⁻⁸, ±10¹⁵⁰, ±10⁻¹⁵⁰, ±∞ and NaN — a hundred and thirty thousand
pairs — agrees exactly, and the product agrees for the 10³⁰⁰ and
subnormal magnitudes too.
§Where it is not the last word
libgcc goes further on the f64 quotient: it prescales both operands
by powers of two before dividing, which buys accuracy in the last corner —
operands whose exponents are hundreds apart, where an intermediate of
Smith’s algorithm underflows even after the refinement above. GCC’s own
gcc.c-torture/execute/ieee/cdivchkd.c is four such quotients, and two of
its four are beyond what is here. C leaves the accuracy of complex
arithmetic implementation-defined (Annex G.6), so this is a quality gap and
not a conformance one; doc/gcc-torture.md records it.
Functions§
- add_
real_ f32 - A
float _Complexvalue plus a real one, which touches the real part only. - add_
real_ f64 - A
double _Complexvalue plus a real one, which touches the real part only. - conj_
f32 - The conjugate of a
float _Complexvalue:conj, and GNU’s~z. - conj_
f64 - The conjugate of a
double _Complexvalue:conj, and GNU’s~z. - div_f32
- The quotient of two
float _Complexvalues (C99 6.5.5, Annex G.5.1). - div_f64
- The quotient of two
double _Complexvalues (C99 6.5.5, Annex G.5.1). - div_
real_ f32 - A
float _Complexvalue divided by a real one, which C computes componentwise. - div_
real_ f64 - A
double _Complexvalue divided by a real one, which C computes componentwise. - eq_f32
- Whether two
float _Complexvalues are equal: C99 6.5.9p3, both parts equal. - eq_f64
- Whether two
double _Complexvalues are equal: C99 6.5.9p3, both parts equal. - mul_f32
- The product of two
float _Complexvalues (C99 6.5.5, Annex G.5.1). - mul_f64
- The product of two
double _Complexvalues (C99 6.5.5, Annex G.5.1). - mul_
real_ f32 - A
float _Complexvalue times a real one, which C computes componentwise. - mul_
real_ f64 - A
double _Complexvalue times a real one, which C computes componentwise. - narrow_
f64 double _Complexnarrowed tofloat _Complex.- ne_f32
- Whether two
float _Complexvalues differ — the negation ofeq_f32, and!=in C. - ne_f64
- Whether two
double _Complexvalues differ — the negation ofeq_f64, and!=in C. - nonzero_
f32 - Whether a
float _Complexvalue is non-zero, which is what C’s conversion to_Booland a controlling expression ask (C99 6.3.1.2). - nonzero_
f64 - Whether a
double _Complexvalue is non-zero, which is what C’s conversion to_Booland a controlling expression ask (C99 6.3.1.2). - proj_
f32 - The projection of a
float _Complexvalue onto the Riemann sphere: C99 7.3.9.5’scproj. - proj_
f64 - The projection of a
double _Complexvalue onto the Riemann sphere: C99 7.3.9.5’scproj. - real_
add_ f32 - A real value plus a
float _Complexone; seereal_mul_f32for why the order matters. - real_
add_ f64 - A real value plus a
double _Complexone; seereal_mul_f64for why the order matters. - real_
div_ f32 - A real value divided by a
float _Complexone. - real_
div_ f64 - A real value divided by a
double _Complexone. - real_
mul_ f32 - A real value times a
float _Complexone: the same product, with the operands in source order so that the generated code evaluates them where they were written. - real_
mul_ f64 - A real value times a
double _Complexone: the same product, with the operands in source order so that the generated code evaluates them where they were written. - real_
sub_ f32 - A real value minus a
float _Complexone, which negates the imaginary part. - real_
sub_ f64 - A real value minus a
double _Complexone, which negates the imaginary part. - sub_
real_ f32 - A
float _Complexvalue minus a real one, which touches the real part only. - sub_
real_ f64 - A
double _Complexvalue minus a real one, which touches the real part only. - widen_
f32 float _Complexwidened todouble _Complex.