Maynard A. Wright is a self-employed consultant in telecommunications based in Citrus Heights, California with 28 years of experience in the industry. Wright is registered as a professional electical engineer in California and is currently active in two working groups which are developing national standards for telecommunications (ANSI/ECSA TIM1.3 and IEEE P743). He was awarded the Centennial Medal of the Institute of Electrical and Electronics Engineers (IEEE) in 1984 and currently holds both local and national positions in IEEE. You can contact him at 6930 Enright Drive, Citrus Heights, CA 95621 (916) 726-1673.
As an engineer I occasionally program, usually to model some aspect of a communications system. Until recently I used FORTRAN almost exclusively because much of what I do involves manipulating complex variables. For short throwaway programs I have often used BASIC. FORTRAN's convenient operations on variables of complex type are superior to those in BASIC. The BASIC code is rife with the sines, cosines and arctangents that are required to implement polar-to-rectangular and rectangular-to-polar conversions, and the code is not nearly as readable as its FORTRAN counterpart.
A recent project required writing code for a client's staff who didn't speak FORTRAN. I bought a Microsoft QuickC compiler and quickly found that tasks which are best done in FORTRAN by linking in an assembly routine can be done directly in C. QuickC code is also portable to other compilers and systems.
Unfortunately for me, C lacks a complex data type. I solved this by using the same techniques that work well in BASIC. A language as versatile as C must have a better solution.
David Messerschmitt [1] proposes passing structures to and returning structures from functions to produce readable complex functions. This method provides source code that seems every bit as readable as that of FORTRAN:
FORTRAN: Z = X + Y C: z = cmplx_add(x, y);where x, X, y, Y, z and Z are complex. Although the FORTRAN code is identical to an algebraic expression, the C code is equally clear as long as the reader is aware of the complex library functions prefixed by cmplx_ or by the letter c. BASIC or C code without the complex data type would require several statements to perform the same computation, depending on whether the arguments were available in rectangular or polar form and on the desired form of the return.Although Messerschmitt's method seems an excellent way to handle complex functions in C, at least some C texts imply dark happenings if structures are passed to and/or returned from functions. Most of the warnings include the qualifier "long," but at least one reference, the QuickC v1.01 Programmer's Guide [2], states that you should not write functions that have structure or union return types due to the large size of such objects. The reader is left, however, with a sense that functional doom awaits those who ignore such admonitions.
The warnings left me a little uneasy about writing my own C complex function library in order to cast off my last ties to FORTRAN. How large is long when applied to a structure? Two doubles occupy 16 bytes on my system. Is that long in the sense of the concern about structure arguments and returns?
[Editor's note: The sky won't fall, but programmers who return structures risk creating non-portable code and inefficient code. One cannot portably return structures because many preANSI compilers did not support structure passing by value. Compilers that do support structure passing usually do so with some type of copy mechanism. It is potentially very inefficient to copy an entire structure from the calling routine into a local space and then copy the structure back at exit.]
In order to test the waters a bit, I wrote three versions of a function to calculate the complex hyperbolic sine of a complex argument: one which accepts a structure containing two doubles and returns a structure of the same form as proposed by Messerschmitt, a second which accepts a pointer to a global array containing two doubles and returns a pointer to another such array, and a third which obtains space for doubles representing arguments and returns using malloc() and accepts and returns pointers to that space.
The source code (Listing 1, Listing 2, and Listing 3) was compiled and linked using QuickC's QCL.EXE. The computation of the complex hyperbolic sine was performed 5,000 times in a for loop in each version. Execution was timed using getticks() as described in Proficient C, pages 144-146 [3]. Since I only wanted to compare the three versions' runtime converting the BIOS ticks to actual elapsed time was unnecessary. Table 1 contains the values obtained by compiling, linking and running the functions on an 8088 XT clone with an installed 8087.
Listing 3's longer runtime results from calling malloc() and then free() during every iteration of the program. Using pointers to global arrays seems to provide about a five percent improvement in execution speed over passing and returning structures. Using the array pointers, however, makes the source code considerably less readable and makes functions considerably less independent. This militates in favor of using Messerschmitt's structures.
Armed with this data, I wrote a complex function library containing complex add, subtract, multiply and divide and most of the various transcendental functions. Listing 4 contains the source code for the library, and Listing 5 shows the associated header file. I have thoroughly tested functions ctanh (complex hyperbolic tangent) and catnh (inverse complex hyperbolic tangent). All the other functions in the library have been tested to at least some extent.
To avoid conflict with the complex structure type of the Microsoft C library declared in math.h, the structure type used by the complex function library is named cmplx_nmbr. Using Microsoft's declaration of type complex in math.h is tempting, but doing so inhibits porting to other compilers because the complex type is not part of the ANSI standard. Microsoft uses the complex type only in implementing the cabs() function, which accepts a structure containing two doubles and returns a single double.
The complex function library includes a rudimentary error handler err_hndlr(), which is invoked if errno is non-zero after computing the return value of each function. For most applications I envision, terminating execution seems appropriate for any call to err.hndlr(); therefore, the function is written to carry out that action.
The library as presented here does not include all possible transcendental functions. The code for additional functions may be added by looking up the appropriate closed form expressions in mathematical reference works. Functions which do not exist in closed form may be calculated by using truncated infinite series.
With some compilers, there may be considerable benefit derived from specifying the various functions as independent source files and building a library from the resulting independent object modules. I have not done this primarily to keep all the source code in one file.
About the time I thought I had this all wrapped up, Louis Baker introduced his efficient complex arithmetic and matrix macros [5]. I wrote another inverse complex hyperbolic sine routine using his technique and compared it with the other three. The source code, in Listing 6, results in
Baker .OBJ file size 1470 .EXE file size 24210 run time in BIOS ticks 180Although the correspondence between Listing 6 and Listing 1 through Listing 3 may not be exact, Listing 6 is the fastest of them all. The code, however, does not allow a function return to be shown as an lvalue. For maximum readability I use Messerschmitt's structure passing techniques. To squeeze maximum efficiency out of a program, I use Baker's defines or some combination of the techniques already discussed.In C, complex structures can be defined in any manner the user desires. The FORTRAN compiler I use requires the two components of a variable declared COMPLEX to be REAL*4, which is comparable to type float in C. This limitation can be quite serious when writing code to perform tasks such as the analysis of transmission lines with high-standing wave ratios. Such lines exhibit impedances with very high and very low values, which may cause loss-of-precision errors when only single-precision variables are available.
zin.c (Listing 7) is a transmission line input impedance program which uses the complex function library. Zin will work for the complex impedances encountered in telephone cable pairs as well as with the high-frequency approximations appropriate to radio transmission lines.
Zin tandems transmission line sections together to solve such problems as the input impedance of a quarter-wave matching section or the series connection of cable pairs of differing impedances. Zin avoids divide by zero errors by accepting zero impedance inputs and adding a very small number to such an input.
Zin implements Equation 7.19 of [4]:
Zi / Zo = tanh[(a + jb)d + arctanh(Zt / Zo)]where
Zi = the input impedance to the line Zo = the characteristic impedance of the line Zt = the impedance terminating the line a = the attenuation constant of the line b = the phase constant of the line d = the length of the lineNote that if, in FORTRAN, a + jb (the complex propagation constant of the transmission line or cable pair) is moved into a complex variable CPROP, the code for the preceding equation is:
ZI : (CTANH(CPROP*L+CATNH(ZT/ZO))*ZOwhere:
CTANH() = complex hyperbolic tangent function CATNH() = inverse complex hyperbolic tangent function L = the length of the line (usually as a REAL*4)This code is almost identical to the algebraic form and is very readable. None of the C techniques resemble the algebraic form as much as does one possible FORTRAN implementation.The terminal interface portion of zin is relatively user-friendly and rejects inputs that cannot be converted to valid doubles via the keyboard input function get_real(). Zin and get_real() use Microsoft cursor positioning commands. Though these commands reduce portability, porting to Mix Power C requires only a few minutes.
Zin could be modified to eliminate non-ANSI functions, but the user-friendliness of get_real() depends on repositioning the cursor either following an input error diagnostic message or on completion of input in order to write the converted value over what was actually entered.
Summary
The complex function library discussed and presented here is something of an experiment in C. The complex function library has been useful in implementating programs such as zin and in wooing me away from FORTRAN.
References
[1] David G. Messerschmitt, "A Transmission Line Modeling Program Written in C," IEEE Journal on Selected Areas in Communications, Vol. SAC-2, No. 1, January, 1984.[2] Microsoft QuickC Programmer's Guide, Microsoft Corporation, 1987.
[3] Augie Hansen, Proficient C, Microsoft Press, 1987.
[4] Robert A. Chipman, Theory and Problems of Transmision Lines, Schaum's Outline Series, McGraw-Hill, 1968.
[5] Louis Baker, "Complex Arithmetic And Matrices In C," The C Users Journal, May, 1990.
Sidebar: Comples Number Applications