Extending Interpreters:
Adding Complex Arithmetic
Although the addition of complex arithmetic to an interpreter appears
at first to be a significant modification, as it happens, except for
the addition of one line of code in the constructor for the
Dataset class which predefines
i, all of the modifications are confined to the Value class. Although these modifications
are extensive, they should be moderately clear to any experienced
programmer with a basic understanding of complex arithmetic.
Since the modifications are confined to the files
comdefs.h and comdefs.cpp and require no syntactic changes, it is possible
to add complex arithmetic to any of the four interpreters in the kit by
replacing comdefs.h and comdefs.cpp with the complex versions of these
files: complex\comdefs.h and complex\comdefs.cpp.
One of the principal design criteria of the Extensible Interpreter
Development Kit was that adding new functionality should consist primarily
of adding code to handle the new functionality, not changing
existing code. As can be seen by inspecting the summary of changes that
follows, this criterion has largely been satisfied except where there are
necessary interactions between the existing integerType and realType types and the new complexType.
Modifications to the Value Class
The approach taken to add complex arithmetic support is to modify the Value class so that a value object may
contain a complex value. Note that this is substantially more complicated
than adding array support. The reason is that support for complex values
interacts strongly with the code for integer and real data types.
The modifications required to support complex
arithmetic are as follows:
-
Add a new type, complexType, to the
Type enumeration.
-
Add a struct to the anonymous
union that provides storage for the real and imaginary parts of
a complex number. The real and imaginary parts of the complex number
may be accessed as part.real and part.imag.
Note that because of the way unions work in C/C++,
the real part can be referred to either as value or as
part.real.
-
Add a constructor to the Value class
that will create a Value object containing a complex value.
-
Modify all other constructors so they initialize the imaginary part to
zero. In principle, this is necessary only for integerType
and realType constructors, however it seems good
programming practice to do it in all cases.
-
Add complexType cases to the switch statements in the following functions.
-
Add function objects and entries in the
functionTable to handle the
following functions:
-
In addition to the new constructor,
add the following new functions to the Value class to support the
above modifications:
-
Add string constants for error messages corresponding to new error
modes:
- A complex (or real or integer) operand is required
- A real (or integer) operand is required
Value(double x, Type t);
-
Change assertScalar()
to assertComplex(), since it is
legitimate to initialize a complex value with a purely real value.
Value(const Value &v);
-
Add code to copy the imaginary part of the argument value.
int isTrue() const ;
-
Return true if either the real part or the imaginary part is not zero.
const Value &print(ostream &f) const;
-
Add a call to complexToString()
to get a string representation of the complex number in the form 17 + 42*i
Print the string.
Value &operator = (const Value &v);
-
Set both real and imaginary parts.
Value &operator += (const Value &v) ;
-
There are two cases to be dealt with:
-
The destination is a string and the argument is complex. In this case,
invoke the complexToString()
function to create a string representation of the complex value.
-
The destination is complex. Do the indicated arithmetic. Note that
the imaginary part of an integer or real is zero. Notice that the
value can be promoted.
Value &operator -= (const Value &v) ;
-
Do the indicated arithmetic and promote the type if appropriate.
Value &operator *= (const Value &v) ;
-
Do the indicated arithmetic and promote the type if appropriate.
Value &operator /= (const Value &v);
-
Do the indicated arithmetic and promote the type if appropriate.
- Comparison Operators
-
Complex numbers are not ordered, so only equality and inequality operators
are implemented.
void assertComplex() const;
-
Throws an InterpreterError exception if the value cannot be construed
to be complex.
double magnitudeSquared() const;
-
Calculates the square of the magnitude of a complex number.
double magnitude() const ;
-
Calculates the magnitude of a complex number.
double getReal() const ;
-
Returns the real part of a complex number.
double getImag() const;
-
Returns the imaginary part of a complex number.
char *complexToString(char *buf) const;
-
Creates a string representation of a complex number.
The following additional modifications were required:
-
In the constructor for the Dataset
class, add a single statement:
value("i") = Value(0,1);
This statement predefines the variable i and assigns to it a
value with the real part = 0 and the imaginary part = 1, in other words,
this sets i to its customary value in complex arithmetic. Note
that electrical engineers might, for the sake of tradition, wish to
change this variable to j. If this is done, the complexToString() function should also be
changed to use j instead of i.
-
Additionally, the functions defined in the
FunctionTable
had to be modified to accommodate complex arguments and complex results.
For an explanation of the changes, see any elementary calculus textbook.
|