parker@mars.njit.edu (Bruce Parker) (07/17/89)
Given the current discussion concerning the implementation of complex numbers as ADTs in Turbo 5.5 Pascal, I thought the following code might be useful to the discussion. I have used this code as an example of an ADT in my classes. My feeling lately is that it is too complex, if you'll pardon the pun, to be introduced early on in a class where ADTs are to introduced and implemented, such as a data structures class. My orientation is that of having taught Modula-2 for a few years, so my first inclination was to break each ADT into two files: an interface file (complex.int) which is included into an implementation file (complex.pas). In constructing a library, the interface file would be available along with the compiled object code for the ADT unit. The entire program, including the test driver, can be compiled by typing tpc testcomp /m Any comments are welcome, as I am both a software engineering and Turbo Pascal novice. I'd also be happy to explain or discuss any part of this. Bruce Parker 305 Weston Hall (201) 596-3369 Computer and Information Science Department parker@mars.njit.edu New Jersey Institute of Technology Newark, New Jersey 07102 #!/bin/sh # to extract, remove the header and type "sh filename" if `test ! -s ./complex.int` then echo "writing ./complex.int" cat > ./complex.int << '\End\Of\Shar\' (* * ELEMENTS T * * DOMAIN Complex Numbers, limited by finiteness of floating point * number representation. * * STRUCTURE Opaque * *) TYPE ErrorCode = ( (* Programming errors *) DivisionByZero, (* attempt to divide by zero *) LogarithmOfZero, (* attempt to take the logarithm of zero *) Undefined, (* undefined value, e.g., Argument( 0 ) *) NoError ); PROCEDURE Create( (* out *) VAR z : T ); (* * MODIFIES allocates z *) PROCEDURE Destroy( (* in/out *) VAR z : T ); (* * MODIFIES deallocates z *) PROCEDURE Assign( (* in/out *) VAR z : T; (* in *) x : REAL; (* in *) y : REAL ); (* * REQUIRES Defined( z ) * MODIFIES z's real and imaginary parts are set to x and y, respectively. *) FUNCTION RealPart( (* in *) z : T ): REAL; (* * REQUIRES Defined( z ) and Assigned( z ) * EFFECTS returns real part of complex number z *) FUNCTION ImaginaryPart( (* in *) z : T ): REAL; (* * REQUIRES Defined( z ) and Assigned( z ) * EFFECTS returns imaginary part of complex number z *) FUNCTION Modulus( (* in *) z : T ): REAL; (* * REQUIRES Defined( z ) and Assigned( z ) * EFFECTS returns modulus of complex number z *) FUNCTION Argument( (* in *) z : T; (* out *) VAR result : REAL ): ErrorCode; (* * REQUIRES Defined( z ) and Assigned( z ) * EFFECTS sets result to complex sin of z * EXCEPTIONS Undefined ( z ~ 0 ) *) FUNCTION Conjugate( (* in *) z : T ): T; (* * REQUIRES Defined( z ) and Assigned( z ) * EFFECTS returns complex conjugate of z *) FUNCTION Add( (* in *) x : T; (* in *) y : T ): T; (* * REQUIRES Defined( x ), Defined( y ), Assigned( x ), Assigned( y ) * EFFECTS returns complex sum of complex numbers x and y *) FUNCTION Multiply( (* in *) x : T; (* in *) y : T ): T; (* * REQUIRES Defined( x ), Defined( y ), Assigned( x ), Assigned( y ) * EFFECTS returns complex product of complex numbers x and y *) FUNCTION Divide( (* in *) x : T; (* in *) y : T; (* out *) VAR result : T ): ErrorCode; (* * REQUIRES Defined( x ), Defined( y ), Assigned( x ), Assigned( y ) * EFFECTS sets result to complex quotient of complex numbers x and y * EXCEPTIONS DivisionByZero *) FUNCTION CExp( (* in *) z : T ): T; (* * REQUIRES Defined( z ) and Assigned( z ) * EFFECTS returns complex exponential of complex number z *) FUNCTION CLog( (* in *) z : T; (* out *) VAR result : T ): ErrorCode; (* * REQUIRES Defined( z ) and Assigned( z ) * EFFECTS sets result to complex logarithm of complex number z * EXCEPTIONS LogarithmOfZero *) FUNCTION Power( (* in *) x : T; (* in *) y : T; (* out *) VAR result : T ): ErrorCode; (* * REQUIRES Defined( x ), Defined( y ), Assigned( x ), Assigned( y ) * EFFECTS sets result to complex number x raised to * power of complex number y * EXCEPTIONS LogarithmOfZero ( tries to compute Log( x ) ) *) FUNCTION CCos( (* in *) z : T ): T; (* * REQUIRES Defined( z ) and Assigned( z ) * EFFECTS returns complex cosine of complex number z *) FUNCTION CSin( (* in *) z : T ): T; (* * REQUIRES Defined( z ) and Assigned( z ) * EFFECTS returns complex sin of complex number z *) \End\Of\Shar\ else echo "will not over write ./complex.int" fi if `test ! -s ./complex.pas` then echo "writing ./complex.pas" cat > ./complex.pas << '\End\Of\Shar\' UNIT Complex; INTERFACE TYPE ComplexRecord = RECORD RealPart : REAL; ImaginaryPart : REAL; END; T = ^ComplexRecord; {$I Complex.int} IMPLEMENTATION (* * This module implements complex numbers as an abstract data type. * Each complex number is represented pointer to a record containing * a ( real, imaginary ) pair. Disposal is left to the memory manager. *) CONST (* * A feable attempt to handle "near-zero" values. * In truth, this is hardware dependent. Relevant code should * respond to hardware exceptions, instead of second guessing * the hardware dependencies. On most of 32-bit machines this * will be too conservative. For small machines, this may not * be enough ( <-- boo! hiss! ) *) Epsilon = 1.0e-10; PROCEDURE Create( (* out *) VAR z : T ); BEGIN NEW( z ); END; (* Create *) PROCEDURE Destroy( (* in/out *) VAR z : T ); BEGIN DISPOSE( z ); END; (* Destroy *) PROCEDURE Assign( (* out *) VAR z : T; (* in *) x : REAL; (* in *) y : REAL ); (* Assigns the parameters x and y as the real and imaginary parts, respectively, of the complex number z. *) (* assumptions: z has already been created. *) BEGIN z^.RealPart := x; z^.ImaginaryPart := y END; (* Assign *) FUNCTION RealPart( (* in *) z : T ): REAL; BEGIN RealPart := z^.RealPart END; (* RealPart *) FUNCTION ImaginaryPart( (* in *) z : T ): REAL; BEGIN ImaginaryPart := z^.ImaginaryPart END; (* ImaginaryPart *) FUNCTION Modulus( (* in *) z : T ): REAL; BEGIN Modulus := SQRT( z^.RealPart * z^.RealPart + z^.ImaginaryPart * z^.ImaginaryPart ) END; (* Modulus *) FUNCTION Argument( (* in *) z : T; (* out *) VAR result : REAL ): ErrorCode; BEGIN IF Modulus( z ) < Epsilon THEN Argument := Undefined ELSE BEGIN Argument := NoError; IF ABS( z^.ImaginaryPart ) < Epsilon THEN result := 0.0 ELSE result := ARCTAN( z^.RealPart / z^.ImaginaryPart ) END END; (* Argument *) FUNCTION Conjugate( (* in *) z : T ): T; VAR ConjugateZ : T; BEGIN Create( ConjugateZ ); Assign( ConjugateZ, z^.RealPart, - z^.ImaginaryPart ); Conjugate := ConjugateZ END; (* Conjugate *) FUNCTION Add( (* in *) x : T; (* in *) y : T ): T; VAR sum : T; BEGIN Create( sum ); Assign( sum, x^.RealPart + y^.RealPart, x^.ImaginaryPart + y^.ImaginaryPart ); Add := sum END; (* Add *) FUNCTION Multiply( (* in *) x : T; (* in *) y : T ): T; VAR product : T; BEGIN Create( product ); Assign( product, x^.RealPart * y^.RealPart - x^.ImaginaryPart * y^.ImaginaryPart, x^.RealPart * y^.ImaginaryPart + x^.ImaginaryPart * y^.RealPart ); Multiply := product END; (* Multiply *) FUNCTION Reciprocal( (* in *) z : T; (* out *) VAR result : T ): ErrorCode; (* This function is used in computing the quotient of two complex numbers. *) VAR reciprocalZ : T; modulusSquared : REAL; BEGIN NEW( reciprocalZ ); modulusSquared := z^.RealPart * z^.RealPart + z^.ImaginaryPart * z^.ImaginaryPart; (* Ugh! This test for division by zero should be hardware dependent. *) IF modulusSquared > Epsilon THEN BEGIN reciprocalZ^.RealPart := z^.RealPart / modulusSquared; reciprocalZ^.ImaginaryPart := - z^.ImaginaryPart / modulusSquared; result := reciprocalZ; Reciprocal := NoError END ELSE Reciprocal := DivisionByZero END; (* Reciprocal *) FUNCTION Divide( (* in *) x : T; (* in *) y : T; (* out *) VAR result : T ): ErrorCode; VAR reciprocalY : T; exception : ErrorCode; BEGIN exception := Reciprocal( y, reciprocalY ); Divide := exception; IF exception = NoError THEN result := Multiply( x, reciprocalY ) END; (* Divide *) FUNCTION CExp( (* in *) z : T ): T; VAR expZ : T; BEGIN Create( expZ ); Assign( expZ, EXP( z^.RealPart ) * COS( z^.ImaginaryPart ), EXP( z^.RealPart ) * SIN( z^.ImaginaryPart ) ); CExp := expZ END; (* CExp *) FUNCTION CLog( (* in *) z : T; (* out *) VAR result : T ): ErrorCode; VAR logZ : T; exception : ErrorCode; argumentZ : REAL; BEGIN IF Modulus( z ) > Epsilon THEN BEGIN exception := Argument( z, argumentZ ); CLog := exception; IF exception = NoError THEN BEGIN Create( logZ ); Assign( logZ, LN( Modulus( z ) ), argumentZ ); result := logZ END END ELSE CLog := LogarithmOfZero END; (* Log *) FUNCTION Power( (* in *) x : T; (* in *) y : T; (* out *) VAR result : T ): ErrorCode; VAR logX : T; exception : ErrorCode; BEGIN exception := CLog( x, logX ); IF exception = NoError THEN result := CExp( Multiply( y, logX ) ); Power := exception END; (* Power *) FUNCTION Cosh( (* in *) x : REAL ) : REAL; BEGIN Cosh := ( EXP( x ) + EXP( -x ) ) / 2.0 END; (* Cosh *) FUNCTION Sinh( (* in *) x : REAL ) : REAL; BEGIN Sinh := ( EXP( x ) - EXP( -x ) ) / 2.0 END; (* Sinh *) FUNCTION CSin( (* in *) z : T ): T; VAR sinZ : T; BEGIN Create( sinZ ); Assign( sinZ, SIN( z^.RealPart ) * Cosh( z^.ImaginaryPart ), COS( z^.RealPart ) * Sinh( z^.ImaginaryPart ) ); CSin := sinZ END; (* CSin *) FUNCTION CCos( (* in *) z : T ): T; VAR cosZ : T; BEGIN Create( cosZ ); Assign( cosZ, COS( z^.RealPart ) * Cosh( z^.ImaginaryPart ), - SIN( z^.RealPart ) * Sinh( z^.ImaginaryPart ) ); CCos := cosZ END; (* CCos *) END. (* Complex *) \End\Of\Shar\ else echo "will not over write ./complex.pas" fi if `test ! -s ./testcomp.pas` then echo "writing ./testcomp.pas" cat > ./testcomp.pas << '\End\Of\Shar\' PROGRAM TestComplex( input, output ); (* A driver for testing the complex number dictionary module. * The program is self-documenting. It has on-line documentation * and somewhat reasonable error messages. *) USES Complex; TYPE FormatType = ( cartesian, polar ); (* input/output format for complex numbers. *) CommandType = ( format, conjugate, add, multiply, divide, exp, power, log, cosine, sine, help, invalid, quit ); CommandSet = SET OF CommandType; VAR command : CommandType; complexFormat : FormatType; exception : Complex.ErrorCode; x1 : REAL; (* first component of first complex number *) x2 : REAL; (* second component of first complex number *) y1 : REAL; (* first component of second complex number *) y2 : REAL; (* second component of second complex number *) result : Complex.T; (* complex result of Complex function *) done : BOOLEAN; sc : REAL; PROCEDURE ReadString( (* out *) VAR text : STRING ); (* * REQUIRES OPEN( Input ) * MODIFIES alters file pointer to position after next non-blank string * EFFECTS sets text to next non-blank string of characters *) VAR nextChar : CHAR; whichChar : INTEGER; BEGIN text := ''; (* deblank the input *) nextChar := ' '; WHILE NOT EOLN( Input ) AND ( nextChar = ' ' ) DO Read( Input, nextChar ); text[ 1 ] := nextChar; whichChar := 1; WHILE ( NOT EOLN( Input ) ) AND ( nextChar <> ' ' ) DO BEGIN Read( Input, nextChar ); IF nextChar <> ' ' THEN BEGIN whichChar := whichChar + 1; text[ whichChar ] := nextChar END END; text[ 0 ] := CHR( whichChar ) END; (* ReadString *) PROCEDURE WriteString( (* in *) text : STRING ); (* * REQUIRES OPEN( Output ) * MODIFIES outputs the given non-blank string of characters to Output *) VAR whichChar : INTEGER; BEGIN FOR whichChar := 1 TO LENGTH( text ) DO Write( output, text[ whichChar ] ) END; (* WriteString *) FUNCTION GetCommand : CommandType; (* * REQUIRES Open( Input ) * MODIFIES alters file pointer to point to position following * the current command * EFFECTS returns CommandType of current command. *) VAR inputString : STRING; BEGIN ReadString( inputString ); IF ( inputString = 'quit' ) OR ( inputString = 'q' ) THEN GetCommand := quit ELSE IF ( inputString = 'help' ) OR ( inputString = '?' ) THEN GetCommand := help ELSE IF ( inputString = 'format' ) OR ( inputString = 'f' ) THEN GetCommand := format ELSE IF ( inputString = 'conjugate' ) OR ( inputString = 'C' ) THEN GetCommand := conjugate ELSE IF ( inputString = 'add' ) OR ( inputString = '+' ) THEN GetCommand := add ELSE IF ( inputString = 'multiply' ) OR ( inputString = '*' ) THEN GetCommand := multiply ELSE IF ( inputString = 'divide' ) OR ( inputString = '/' ) THEN GetCommand := divide ELSE IF ( inputString = 'exp' ) OR ( inputString = 'e' ) THEN GetCommand := exp ELSE IF ( inputString = 'power' ) OR ( inputString = '^' ) THEN GetCommand := power ELSE IF ( inputString = 'log' ) OR ( inputString = 'l' ) THEN GetCommand := log ELSE IF ( inputString = 'cos' ) OR ( inputString = 'c' ) THEN GetCommand := cosine ELSE IF ( inputString = 'sin' ) OR ( inputString = 's' ) THEN GetCommand := sine ELSE GetCommand := invalid END; (* GetCommand *) PROCEDURE PrintHelpMessage; (* * MODIFIES prints help message on Output *) BEGIN WriteLn( output, ' Command' ); WriteLn( output, 'Command Alias Synopsis' ); WriteLn( output, '------- ------- --------' ); WriteLn( output, 'add <z1> <z2> + Add <z1> and <z2>' ); WriteLn( output, 'conjugate <z> C Form complex conjugate of <z>' ); WriteLn( output, 'cos <z> c Cosine of <z>' ); WriteLn( output, 'divide <z1> <z2> / Divide <z1> by <z2>'); WriteLn( output, 'exp <z> e Raise e to the <z>th power' ); WriteLn( output, 'help ? Print this message' ); WriteLn( output, 'format [xy|polar|?] f Set the complex number format' ); WriteLn( output, ' xy or cartesian for real-imaginary pair' ); WriteLn( output, ' polar for polar coordinates' ); WriteLn( output, ' ? to display current format' ); WriteLn( output, 'log <z> l Logarithm of <z>' ); WriteLn( output, 'multiply <z1> <z2> * Multiply <z1> by <z2>' ); WriteLn( output, 'power <z1> <z2> ^ Raise <z1> to the <z2>th power' ); WriteLn( output, 'quit q Quit' ); WriteLn( output, 'sin <z> s Sine of <z>' ) END; (* PrintHelpMessage *) FUNCTION SetValue( (* in *) x : REAL; (* in *) y : REAL; (* in *) complexFormat : FormatType ) : Complex.T; (* * EFFECTS Assign the parameters x and y to a new complex number * and return y. The parameters are interpreted according * to the current format, i.e., cartesian or polar coordinates. *) VAR z : Complex.T; BEGIN Complex.Create( z ); CASE complexFormat OF cartesian : Complex.Assign( z, x, y ); polar : Complex.Assign( z, x * COS( y ), x * SIN( y ) ) END; SetValue := z END; (* SetValue *) FUNCTION FirstComponent( (* in *) z : Complex.T; (* in *) complexFormat : FormatType ) : REAL; (* * REQUIRES Created( z ), Assigned( z ) * EFFECTS return the first co-ordinate of the given complex number, * translating as necessary according to the current format. *) BEGIN CASE complexFormat OF cartesian : FirstComponent := Complex.RealPart( z ); polar : FirstComponent := Complex.Modulus( z ) END END; (* FirstComponent *) FUNCTION SecondComponent( (* in *) z : Complex.T; (* in *) complexFormat : FormatType; (* out *) VAR result : REAL ) : Complex.ErrorCode; (* * REQUIRES Created( z ), Assigned( z ) * MODIFIES sets 'result' to the second co-ordinate of the given * complex number, translating as necessary according * to the current format. *) BEGIN CASE complexFormat OF cartesian : BEGIN SecondComponent := Complex.NoError; result := Complex.ImaginaryPart( z ) END; polar : SecondComponent := Complex.Argument( z, result ) END END; (* SecondComponent *) PROCEDURE PrintErrorMessage( (* in *) exception : Complex.ErrorCode ); (* * MODIFIES converts Complex.ErrorCode to string and prints it * on Output *) BEGIN CASE exception OF Complex.DivisionByZero : WriteLn( Output, 'divide by zero' ); Complex.LogarithmOfZero : WriteLn( Output, 'logarithm of zero' ); Complex.Undefined : WriteLn( Output, 'undefined' ) END END; (* PrintErrorMessage *) PROCEDURE SetFormat( (* in/out *) VAR complexFormat : FormatType ); (* * REQUIRES Open( Input ) * EFFECTS Decodes and records new format for complex numbers, * or prints current format if queried with "?". * Input file pointer will point to blank following non-blank * string. * EXCEPTIONS Error messages printed if argument is invalid. *) VAR newFormat : STRING; valueCount : INTEGER; BEGIN ReadString( newFormat ); IF ( newFormat = 'cartesian' ) OR ( newFormat = 'xy' ) THEN complexFormat := cartesian ELSE IF newFormat = 'polar' THEN complexFormat := polar ELSE IF newFormat = '?' THEN BEGIN Write( Output, 'current format is ' ); CASE complexFormat OF cartesian : WriteLn( Output, 'cartesian' ); polar : WriteLn( Output, 'polar' ) END END ELSE WriteLn( Output, 'invalid format specification' ) END; (* SetFormat *) BEGIN WriteLn( output, 'Complex Number Test Module' ); WriteLn( output, '--------------------------' ); WriteLn( output, 'Enter ? for help.' ); Write( output, '> ' ); complexFormat := cartesian; done := FALSE; WHILE ( NOT EOF( input ) ) AND ( NOT done ) DO BEGIN exception := Complex.NoError; command := GetCommand; (* * Handle parameterless commands *) IF command IN [ format, help, invalid, quit ] THEN CASE command OF format : SetFormat( complexFormat ); help : PrintHelpMessage; invalid : WriteLn( Output, 'invalid command' ); quit : done := TRUE END (* * Handle commands with parameters *) ELSE BEGIN Read( Input, x1, x2 ); (* * Handle commands with second complex number parameter *) IF command IN [ add, divide, multiply, power ] THEN Read( Input, y1, y2 ); CASE command OF add : result := Complex.Add( SetValue( x1, x2, complexFormat ), SetValue( y1, y2, complexFormat ) ); conjugate : result := Complex.Conjugate( SetValue( x1, x2, complexFormat ) ); cosine : result := Complex.CCos( SetValue( x1, x2, complexFormat ) ); divide : exception := Complex.Divide( SetValue( x1, x2, complexFormat ), SetValue( y1, y2, complexFormat ), result ); exp : result := Complex.CExp( SetValue( x1, x2, complexFormat ) ); log : exception := Complex.CLog( SetValue( x1, x2, complexFormat ), result ); multiply : result := Complex.Multiply( SetValue( x1, x2, complexFormat ), SetValue( y1, y2, complexFormat ) ); power : exception := Complex.Power( SetValue( x1, x2, complexFormat ), SetValue( y1, y2, complexFormat ), result ); sine : result := Complex.CSin( SetValue( x1, x2, complexFormat ) ) END; IF exception = Complex.NoError THEN BEGIN Write( Output, FirstComponent( result, complexFormat ), ' ' ); IF SecondComponent( result, complexFormat, sc ) = Complex.NoError THEN WriteLn( Output, sc ) ELSE WriteLn( Output, ' undefined' ) END ELSE PrintErrorMessage( exception ) END; IF NOT done THEN BEGIN ReadLn( input ); Write( output, '> ' ) END END; IF EOF( input ) THEN WriteLn( output ) END. \End\Of\Shar\ else echo "will not over write ./testcomp.pas" fi echo "Finished archive 1 of 1" exit Bruce Parker 305 Weston Hall (201) 596-3369 Computer and Information Science Department parker@mars.njit.edu New Jersey Institute of Technology