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 IntersectionOur 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 HeaderListing 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++ SectionThe 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 SectionThe 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 FeaturesWe 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 FeaturesThere 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 IntersectionConfining 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 AuthorsRandy 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 firstname.lastname@example.org.
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#.