There are a number of applications in mathematics and physics for which the real number are insufficient. An extended one of these will be considered later in this chapter. From an aesthetic point of view alone, it is convenient to be able to talk about solutions to equations such as x^{2} = -4 in much the same way as equations such as x^{2} = 4. After all, there is not, at first glance, much difference between the two, except that the solution to the former, whatever it is, it is not a real number, because the square of a real number can only be positive. To answer this and other more important needs, we define the complex numbers:

*A Complex number has the form a + bi where a and b are Real numbers and i^{2} = -1. The number "a" is called the real part, and the number "b" is the imaginary part.*

In pictorial form, a complex number can be thought of as being a point in the complex plane. This is defined by two axes, much like a two dimensional real plane, except that only the horizontal axis has real numbers. The vertical or *imaginary* axis is marked in units of *i* (the square root of negative one). Points in the plane can be though of as ordered pairs (a, b), but are normally written in the notation above as *a + bi*.

The complex number zero is defined to be the one that represents the origin in the above diagram, that is a complex number is zero if and only if both its real part and its imaginary part are zero (0 + 0i).

Addition and subtraction of complex numbers is performed in the same way as it is for vectors (see Chapter 7) where the components are added. Thus, in order to add two complex numbers, one just adds the real parts and we add the imaginary parts. That is,

(a + bi) + (c + di) = (a + c) + (b + d)i

To multiply two complex numbers, one treats them as any other binomial expressions and multiplies each term of the first by each term of the second:

(a + bi) * (c + di) = a * (c + di) + bi * (c + di) = (ac - bd) + (ad + bc)i

The reciprocal of a pure imaginary complex number (one with zero real part) is found by multiplying the numerator and denominator by i. Thus:

For other divisions, one multiplies numerator and denominator by the complex conjugate of the denominator. This results in a real denominator and a complex number in the proper form.

* The complex conjugate of a complex number a + bi is the number a - bi.*

In the introductory Calculus course it is shown that certain transcendental functions of real numbers can be expanded (so as to be approximated) as power series in the following way:

If one replaces *x* with *ix* in the latter series, it can readily be seen that:

This last equation is known as Euler's Formula. From it, one also has that

so that it is also possible to write:

These formulas provide a means of taking powers and roots of complex numbers readily. First observe that, if in figure 17.1, the distance from the origin to the point representing the complex number is denoted as *r* and the angle that a ray through the origin and this point makes with the positive real axis is denoted *t*, then as for vectors, one can write:

and

Thus, if , for instance z = (1 - i)^{4} were expressed using these two relations as

then one could write

and this works for fractional exponents as well, allowing one to take various roots, so that, for instance, the sixth root of one can be written as

and since the angle *0* is coterminal with the angles 2¼, 4¼, 6¼, 8¼, and 10¼ there are also roots at

for each of these, yielding

for n = 1,2,3,4, and 5.

At n = 6 the results begin to repeat. After all, there can only be six sixth roots of a number. These roots are therefore :

This discussion of power and root finding has been undertaken mainly as illustration. However, the underlying principles will be returned to later in the chapter.

There are a variety of approaches to implementing complex numbers in non ISO standard versions of Modula-2. One could do so transparently:

DEFINITIONMODULEComplexNumbers; (* by R. Sutcliffe last modified 1996 10 30 *)TYPEParts = (re, im); Complex =ARRAYPartsOFREAL;PROCEDURECmplx (real, imag :REAL) : Complex; (* constructs a complex from an ordered pair *)PROCEDURERe (num : Complex) :REAL; (* returns the real part of a complex number *)PROCEDUREIm (num : Complex) :REAL; (* returns the imaginary part of a complex number *)PROCEDUREAdd (first, second : Complex) : Complex;PROCEDURESubtract (first, second : Complex) : Complex;PROCEDUREMultiply (first, second : Complex) : Complex;PROCEDUREDivide (first, second : Complex) : Complex;ENDComplexNumbers.

Alternately, one could use a record with two fields called *re* and *im*, and otherwise the same arithmetic operations.

The implementation module for this data type as defined above is fairly straightforward, and most of it is shown below:

IMPLEMENTATIONMODULEComplexNumbers; (* by R. Sutcliffe last modified 1996 10 30 *)PROCEDURECmplx (real, imag :REAL) : Complex; (* constructs a complex from an ordered pair *)VARtemp : Complex;BEGINtemp [re] := real; temp [im] := imag;RETURNtemp;ENDCmplx;PROCEDURERe (num : Complex) :REAL; (* returns the real part of a complex number *)BEGINRETURNnum[re];ENDRe;PROCEDUREIm (num : Complex) :REAL; (* returns the imaginary part of a complex number *)BEGINRETURNnum[im];ENDIm;PROCEDUREAdd (first, second : Complex) : Complex;VARtemp : Complex;BEGINtemp [re] := first [re] + second [re]; temp [im] := first [im] + second [im];RETURNtemp;ENDAdd;PROCEDURESubtract (first, second : Complex) : Complex;VARtemp : Complex;BEGINtemp [re] := first [re] - second [re]; temp [im] := first [im] - second [im];RETURNtemp;ENDSubtract;PROCEDUREMultiply (first, second : Complex) : Complex;VARtemp : Complex;BEGINtemp [re] := first [re] * second [re] - first [im] * second [im]; temp [im] := first [re] * second [im] + first [im] * second [re];RETURNtemp;ENDMultiply;PROCEDUREDivide (first, second : Complex) : Complex;VARtemp : Complex;BEGIN(* left as an exercise *)ENDDivide;ENDComplexNumbers.

There are other things that would still have to be taken care of, such as I/O and various mathematical operations, but these could be done in other library modules. In addition, it might be desirable to have a separate ADT called *LongComplex* whose parts were LONGREAL.

For minimal testing purposes at this juncture, it is worth while to include a small application program that makes use of the data type *Complex*. It incidentally addresses the issue of writing complex numbers.

MODULETestComplex; (* by R. Sutcliffe to test transparent implementation of complex numbers modified 1996 10 30 *)FROMComplexNumbersIMPORTComplex, Add, Multiply, Cmplx, Re, Im;FROMSTextIOIMPORTWriteChar, WriteLn, WriteString;FROMSRealIOIMPORTWriteReal;VARnumber1, number2, number3 : Complex;PROCEDUREWriteComplex (number : Complex; flen :CARDINAL); (* Of the space provided in flen, four places are needed to write the + between the two reals, and one to write the letter i. The rest is divided equally between the two reals. *)VARrealFlen :CARDINAL;BEGINIFflen < 4THENrealFlen := 0;ELSErealFlen := (flen - 4)DIV2;END; WriteReal (Re(number), realFlen); WriteString (" + "); WriteReal (Im(number), realFlen); WriteChar ('i');ENDWriteComplex;BEGIN(* Initialize complex numbers *) number1 := Cmplx (4.0, 16.0); number2 := Cmplx (-13.0, 2.0); number3 := Add(number1, number2); WriteString ("The sum is "); WriteComplex (number3, 20); WriteLn; number3 := Multiply (number1, number2); WriteString ("The product is "); WriteComplex (number3, 20); WriteLn;ENDTestComplex.

The output from this program, as expected was:

The sum is -9.00000 + 18.00000i The product is -84.0000 + -200.000i

Notice that the client program "knows" the names of the parts of the type *Complex*. This fact has not actually been used because the procedures *Cmplx*, *Re*, and *Im* were used to construct and find the parts of a complex number, but it could have been. As indicated in earlier chapters, it is a better idea to keep such details hidden, so that the client program does not have access to such information, but rather the data can be operated upon only by the procedures which are also contained in the module along with the data type definition. There have been several instances of such opaque types throughout the text. However, as noted in section 12.8 and illustrated with the module *points* in that chapter, one cannot have a procedure allocate memory for a temporary opaque variable and then return it because every call to such a procedure would use a little more memory unnecessarily, and one cannot deallocate the memory before returning either because then the memory pointed to after the return is no part of a valid allocation. The real problem is that in writing

number3 := Add(number1, number2);

with opaque variables, access to the memory pointed to by *number3* before the assignment is simply lost. This is a consequence of the fact that opaque types are in fact pointers.

Thus, an implementation of complex numbers in classic Modula-2 requires that one use variable parameters in regular procedures rather than function procedures returning an item of the opaque type. Moreover, procedures are needed to initialize and destroy complex entities. We could have:

DEFINITIONMODULEComplexNumbersO; (* by R. Sutcliffe last modified 1996 10 30 *)TYPEComplex; (* no details here *)PROCEDUREInit (VARcomplexToInit : Complex); (* Must be called to create a Complex number. The number created will have both real and imaginary parts zeroed. *)PROCEDUREDestroy (VARcomplexToDestroy : Complex); (* Call to give back the memory allocated to a Complex number *)PROCEDURECmplx (real, imag :REAL;VARresult : Complex); (* Takes two reals as the parts and assigns them to the complex. *)PROCEDURERe (cNumber : Complex) :REAL; (* Returns the real part of the given complex number. *)PROCEDUREIm (cNumber : Complex) :REAL; (* Returns the imaginary part of the given complex number. *)PROCEDURENegate (originalNum: Complex;VARresult : Complex); (* Returns the opposite of the complex number. *)PROCEDUREAdd (firstNum, secondNum : Complex;VARresult : Complex); (* Returns the sum of the two complex numbers. *)PROCEDURESubtract (firstNum, secondNum : Complex;VARresult : Complex); (* Returns the difference of the two complex numbers. *)PROCEDUREMultiply (firstNum, secondNum : Complex;VARresult : Complex); (* Returns the product of the two complex numbers. *)PROCEDUREDivide (firstNum, secondNum : Complex;VARresult : Complex); (* Returns the quotient of the two complex numbers. *)ENDComplexNumbersO.

One must still decide whether to implement the type as an array or a record. A record style implementation is given below.

IMPLEMENTATIONMODULEComplexNumbersO; (* by R. Sutcliffe last modified 1996 10 30 *)FROMStorageIMPORTALLOCATE, DEALLOCATE;TYPEComplex =POINTERTOComplexData; (* opaque, so must be a pointer *) ComplexData = (* now here is its structure *)RECORDre :REAL; im :REAL;END(* record *);PROCEDUREInit (VARcomplexToInit : Complex);BEGINNEW(complexToInit); (* get memory dynamically *) complexToInit^.re := 0.0; (* and zero it off *) complexToInit^.im := 0.0;ENDInit;PROCEDURECmplx (re, im :REAL;VARcomplexToAssign: Complex);BEGINcomplexToAssign^.re := re; complexToAssign^.im := im;ENDCmplx;PROCEDURERe (cNumber : Complex) :REAL;BEGINRETURNcNumber^.re;ENDRe;PROCEDUREIm ( cNumber : Complex) :REAL;BEGINRETURNcNumber^.im;ENDIm;PROCEDURENegate (originalNum: Complex;VARresult : Complex);BEGINresult^.re := -originalNum^.re; result^.im := -originalNum^.im;ENDNegate;PROCEDUREAdd (firstNum, secondNum : Complex;VARresult : Complex);BEGINresult^.re := firstNum^.re + secondNum^.re; result^.im := firstNum^.im + secondNum^.im;ENDAdd;PROCEDURESubtract (firstNum, secondNum : Complex;VARresult : Complex);BEGINresult^.re := firstNum^.re - secondNum^.re; result^.im := firstNum^.im - secondNum^.im;ENDSubtract;PROCEDUREMultiply (firstNum, secondNum : Complex;VARresult : Complex);BEGINresult^.re := firstNum^.re * secondNum^.re - firstNum^.im * secondNum^.im; result^.im := firstNum^.re * secondNum^.im + firstNum^.im * secondNum^.re;ENDMultiply;PROCEDUREDivide (firstNum, secondNum : Complex;VARresult : Complex);BEGINresult^.re := (firstNum^.re*secondNum^.re + firstNum^.im*secondNum^.im)/ (secondNum^.re*secondNum^.re + secondNum^.im*secondNum^.im); result^.im := (firstNum^.im*secondNum^.re + firstNum^.re*secondNum^.im)/ (secondNum^.re*secondNum^.re + secondNum^.im*secondNum^.im);ENDDivide;PROCEDUREDestroy (VARcomplexToDestroy : Complex);BEGINDISPOSE(complexToDestroy);ENDDestroy;ENDComplexNumbersO.

As usual, the implementation could be changed without affecting the definition in any way. Now, consider how the client program must be written with the new regular procedure syntax for the calls:

MODULETestComplexO; (* by R. Sutcliffe last modified 1996 10 30 *)FROMComplexNumbersOIMPORTComplex, Init, Cmplx, Re, Im, Add, Multiply;FROMSTextIOIMPORTWriteLn, WriteString, WriteChar;FROMSRealIOIMPORTWriteReal;VARnumber1, number2, number3 : Complex;PROCEDUREWriteComplex (number : Complex; flen :CARDINAL); (* Of the space provided in flen, four places are needed to write the + between the two reals, and one to write the letter i. The rest is divided equally between the two reals. *)VARrealFlen :CARDINAL;BEGINIFflen < 4THENrealFlen := 0;ELSErealFlen := (flen - 4)DIV2;END; WriteReal (Re(number), realFlen); WriteString (" + "); WriteReal (Im(number), realFlen); WriteChar ('i');ENDWriteComplex;BEGIN(* ClientProgram *) (* Create the complex numbers *) Init (number1); Init (number2); Init (number3); (* Initialize the complex numbers number1 and number2 *) Cmplx (4.0, 16.0, number1); Cmplx (-13.0, 2.0, number2); (* the last part is the same as before. *) Add (number1, number2, number3); WriteString ("The sum is "); WriteComplex (number3, 20); WriteLn; Multiply (number1, number2, number3); WriteString ("The product is "); WriteComplex (number3, 20); WriteLn;ENDTestComplexO.

Having to use such syntax for the manipulation of a numeric type is a nuisance. One does not expect any numeric type to have to be initialized or to be added using three parameter regular procedures. However, if all one has is classical Modula-2 the only choice is to implement transparently or to live with the extra apparatus required by an opaque implementation.

If it is necessary to go further and define arithmetic and mathematical operations on the type complex, that can be done in a separate module. For details on what such a module should contain, see the next section.