Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Complex Arithmetic: In the Intersection of C and C++


The New C: Complex Arithmetic: In the Intersection of C and C++

Many of C99's features came from extensions that vendors had made to their C compilers to permit FORTRAN programmers to move to C. A case in point: Cray Research had FORTRAN programmers who wished to be C programmers, but they needed FORTRAN's support for complex numbers.

C++ has had complex numbers since its early days, so adding complex data types to C99 has had the potential to increase the intersection between C and C++. However, C by necessity took a different approach to supporting complex numbers than C++. C++ added support for complex numbers via the library using classes, templates, and overloaded operators. C99 added complex numbers directly into the language as new built-in types and new rules for handling expressions. C++ uses template syntax for naming complex types, for example, complex<double>. C99 uses the traditional sequence-of-keywords approach, for example, double complex. C++ uses constructors to create complex numbers, complex<double>(1.,2.). C99 does not even have constructors.

At first, the gulf between the class library approach and the built-into-the-language approach seems large. However, these differences are more syntax than semantics; these differences between complex arithmetic in C99 and C++ can be reconciled by a small header file. This should not really be a surprise since one of C++'s design goals was that the programmer should be able in effect to extend the language by writing class types and overloads. The approach used to add a language feature should not matter much.

This month's installment does something a little different. Rather than just discuss a new feature in C99 and occasionally compare it to C++, we are going to concentrate on the intersection between C99 and C++. We are going to discuss how to write programs that use complex arithmetic and compile equally well under either C++ or C99. The key to opening the large intersection between C and C++ complex is the small header file named math-complex.h (shown in Listing 1).

The Intersection

Our header math-complex.h defines three complex types: float_complex, double_complex, and long_double_complex. Each type contains two scalars of the corresponding real type, with a real part and an imaginary part, in Cartesian (rectangular) form. The header provides capabilities (in both C and C++) for initialization in the format of the C++ dyadic constructor:

float_complex x =
  float_complex(1.f, 2.f);
In math-complex.h, the initializer syntax mechanisms will convert the arguments to the specified complex type, so we can write float_complex(1, 2) and omit the floating suffixes.

Both C and C++ provide the usual arithmetic operators: plus, minus, times, divide, unary plus, and unary minus. Both provide the corresponding assignment operators (+=, -=, *=, and /=), as well as ordinary assignment (=). Equality (==) and inequality (!=) are provided, but less than and greater than are not provided. (There is no simple one-dimensional meaning for "less" or "greater" among complex numbers.)

All these operators accept one operand of complex type mixed with an operand of the corresponding real type. For example,

double_complex y = double_complex(3, 4);
// now y contains double_complex(12., 16.)
y *= 4.;
In general, C99 permits trouble-free mixing of operand types, such as adding a float_complex and a double_complex. However, to program in the C/C++ intersection, you should stick to the same type forms (all float, all double, or all long double). Therefore, all your conversions should be shown explicitly, using casts or assignment. Continuing from the previous example, we could write:

long_double_complex z =
  long_double_complex(4, 5);
z += (long_double_complex)y.;
// now z contains
// long_double_complex(16.L, 21.L)
Both C and C++ provide the complex trigonometric and hyperbolic functions (i.e., sin, cos, tan, sinh, cosh, and tanh), but the corresponding inverse functions are not (yet) part of C++ (and are arguably used much less frequently anyway). The most common complex math functions are provided by both C and C++ (i.e., exp, log, and sqrt). The type of the result is the same (complex) type as the operand.

The absolute value function, abs(), is also provided. Note, however, that the absolute value of a complex number is its magnitude in polar form. In other words, it is a real number whose type is the same as the floating-point type underlying the complex number.

The pow(x, y) function may be used to raise x to the y power. Either x or y or both may be complex numbers. If either operand is a complex number, the result is also complex. The same restrictions apply as using complex types in expressions: both operands should be the same complex type or you may mix complex with the corresponding real floating-point type.

Several functions are specifically for the complex domain. In math-complex.h, we have provided them with the C++ form of their names (which omit an unnecessary leading c). We will show the float_complex forms; the corresponding double_complex and long_double_complex versions are obvious.

float imag(float_complex z);
returns the imaginary component of z.

float real(float_complex z);
returns the real component of z.

float arg(float_complex z);
returns the phase angle (the "argument") of z.

float_complex conj(float_complex z);
returns the complex conjugate of z (i.e., float_complex(real(z), -imag(z))).

The real and imag functions are very frequently used to extract the components of complex numbers. For example:

static const long double pi =
  3.141592653589793238462643383279502884197L;
long_double_complex w = exp(long_double_complex(0., pi));
printf("w = %Lf+%Lfi\n", real(w), imag(w));

The Header

Listing 1 shows the math-complex.h header. The name of the header comes from the fact that including the header provides the same benefits as including both <math.h> and <complex.h> for C and <cmath> and <complex> for C++. After including the header, overloads for both the real and complex math functions are available for use.

The C++ Section

The header is divided into two parts by conditional compilation. The first part is the code used when compiling under C++ to provide the compatibility intersection.

The first thing that the C++ section does is to include <cmath> and <complex> to define the complex data types and overloaded functions for both the real and complex types.

The second thing that the C++ section does is to give a using directive that allows types and functions from the C++ library to be accessed without the std:: prefix. (ANSI C++ has placed the standard library into a namespace named std.)

The third thing that the C++ section does is to declare synonyms for the complex types using typedefs. This allows the complex types to be accessed with names like float_complex rather than complex<float>. The typedefs also have another effect. In C++, when you declare a synonym for a class type using typedef, you also implicitly declare synonyms for the constructors of the class type. Thus, you can construct objects of that type using the typedef name. For example, float_complex(1,1) is equivalent to complex<float>(1,1).

The C99 Section

The first thing that the C99 section does is to include a new C99 Standard header named <tgmath.h>, which in turn includes the C99 Standard headers <math.h> and <complex.h>. However, <tgmath.h>'s real purpose is to define overloads for all of the math functions in the C99 library so that the proper function is called based on the function's argument(s). For example, calling sqrt() with a float argument calls sqrtf() to take a float square root. Calling sqrt() with a long double argument calls sqrtl() to take a long double square root. Calling sqrt() with a float complex argument calls csqrtf() to take a float complex square root, and so on. Note that C99 does not provide overload support for user-written functions; it just provides built-in support for overloads in the library via <tgmath.h>.

The second thing that the C99 section does is to declare synonyms for the complex types using typedefs.

The third thing that the C99 section does is to define macros to provide the C equivalent of the C++ constructors for creating complex values from two real floating-point values. You might notice that the macros have the same name as the typedefs. This is not a problem since function-like macros (macros that take arguments) are only expanded by the preprocessor if the name of the macro is immediately followed by an open parenthesis (possibly proceeding whitespace).

The fourth thing that the C99 section does is to provide macros to rename four functions whose names in <tgmath.h> differ from the names of those functions in C++.

The remarkable thing about the header is that it is so short and simple.

Listing 2 is an example program that uses the header. The program can be compiled by C or C++ and can be used to test your C++ or C99 implementation for some flaw that would prevent programming in the intersection.

Listing 3 shows sample output from running the program.

C++ Provisions above the Intersection Features

We turn now from our discussion of the C/C++ intersection to consider what is excluded when you use the intersection. To begin with, there are several additional functions in the C++ complex library (i.e., norm, polar, and log10):

float norm(float_complex z);
returns the squared magnitude of z.

float_complex polar(float rho, float theta);
returns the complex value corresponding to a complex number whose magnitude is rho and whose phase angle is theta.

float_complex log10(float_complex z);
returns the complex base-10 logarithm of x.

If your environment provides both C++ and C99 libraries, an extern "C" wrapper written in C++ could trivially provide these functions to your C99 applications.

On a more fundamental level, the C++ implementation of complex data types uses the C++ template syntax. In theory, this would provide an easy method to define a complex arithmetic on top of some "special" floating-point foundation, such as an interval arithmetic. However, the C++ Standard requires the complex<T> template to work properly only for float, double, and long double, so your "special" complex might or might not work, with no guarantees.

C99 Provisions above the Intersection Features

There are several additional functions in the C99 complex library (i.e., cacos, casin, catan, cacosh, casinh, and catanh -- the inverse trig functions). (These are likely to be added to the next revision of C++ and may already be available as extensions in some C++ libraries.) Again, if your environment provides both C++ and C99 libraries, a wrapper written in C++ could trivially provide these functions to your C++ applications.

More fundamentally, the rules for type balancing and type promotion are built-in in C99, so most combinations of mixed-type operands will silently promote to the "wider" argument type. Usually, this will "do the right thing," but occasionally in numerical work the programmer might appreciate a warning about the mixed precision.

There is another feature of the C99 implementation that we have avoided mentioning -- the (pure) Imaginary type. In theory, this permits C99 complex applications to get the precisely correct result in certain boundary cases of IEEE floating-point complex arithmetic. However, the C99 Standard defines the Imaginary type as optional, not required. Furthermore, to our knowledge, it is not provided in any existing implementation. For these reasons, we consider Imaginary to be of mostly theoretical interest.

Cost/Benefit of Working in the Intersection

Confining our focus to programs that use complex arithmetic, how important are the features that are left out of the intersection? In our opinion, they are not very important. Mixed-mode arithmetic may on balance be a useful shorthand, but fundamentally it provides nothing beyond occasionally eliminating type-balancing casts. The slight differences in the library inventories can often be handled with trivial wrapper functions.

Therefore our conclusion regarding complex arithmetic is to recommend the use of the "intersection" style for complex-arithmetic applications. It's possible that in some environments, the C99 implementation will produce faster code execution. On the other hand, there will doubtless be environments in which there is no C99 implementation. And a C++ implementation that incorporates "small-object" optimization is capable of being just as fast as the C99 implementation. In any event, the application that is written in the "intersection" complex style can be compiled as C++ or compiled as C99, whichever works best for each environment.

About the Authors

Randy Meyers is a consultant providing training and mentoring in C, C++, and Java. He is the current chair of J11, the ANSI C committee, and previously was a member of J16 (ANSI C++) and the ISO Java Study Group. He worked on compilers for Digital Equipment Corporation for 16 years and was Project Architect for DEC C and C++. He can be reached at [email protected].

Dr. Thomas Plum has authored four books on C and co-authored Efficient C (with Jim Brodie) and C++ Programming Guidelines (with Daniel Saks). He has been an officer of the United States and international C and C++ standards committees. His company Plum Hall Inc. provides test suites for C, C++, Java, and C#.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.