Path: utzoo!attcan!uunet!ginosko!ctrsol!cica!iuvax!rutgers!njin!jvnca!njitgw!mars.njit.edu!parker
From: parker@mars.njit.edu (Bruce Parker)
Newsgroups: comp.lang.pascal
Subject: Turbo 5.0 Complex ADT
Keywords: Turbo 5.0, complex numbers, ADT
Message-ID: <644@njitgw.njit.edu>
Date: 17 Jul 89 13:26:20 GMT
Reply-To: parker@mars.njit.edu (Bruce Parker)
Organization: New Jersey Institute of Technology
Lines: 798
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 + Add and ' );
WriteLn( output, 'conjugate C Form complex conjugate of ' );
WriteLn( output, 'cos c Cosine of ' );
WriteLn( output, 'divide / Divide by ');
WriteLn( output, 'exp e Raise e to the 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 l Logarithm of ' );
WriteLn( output, 'multiply * Multiply by ' );
WriteLn( output, 'power ^ Raise to the th power' );
WriteLn( output, 'quit q Quit' );
WriteLn( output, 'sin s Sine of ' )
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