home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fred Fish Collection 1.5
/
ffcollection-1-5-1992-11.iso
/
ff_disks
/
600-699
/
ff695.lha
/
ICalc
/
Docs
/
AdvancedGuide
next >
Wrap
Text File
|
1992-07-13
|
16KB
|
549 lines
=============================================================================
icalc - a complex-number expression parser
by Martin W Scott
Version 2.0
=============================================================================
Advanced Guide
=============================================================================
This document describes the more advanced features of icalc, including
programming constructs and array manipulation, as well as some information
that was left out of the User Guide for clarity and continuity.
The syntax used in icalc is closely related to that of C, with many
exceptions. Information/warnings specifically for people who are familiar
with C will be indicated by lines prefixed with 'C:'.
Parentheses and braces will sometimes be used in examples where they don't
strictly need to be, due to operator precedence rules. They are included for
clarity. (NB: A table detailing relative precedences is in Appendix 1 of
the User Guide.)
Principal value ranges (PVRs)
-----------------------------
The PVRs are defined for the inverse trigonometric functions as follows:
asin: if u + iv = asin(z), then
-PI/2 <= u <= PI/2 if v >= 0
-PI/2 < u < PI/2 if v < 0
acos: if u + iv = acos(z), then
0 <= u <= PI if v >= 0
0 < u < PI if v < 0
atan: if u + iv = atan(z), then
-PI/2 < u < PI/2
Additional assignment operators
-------------------------------
As well as the standard = operator, icalc comes with some others both to
improve readability and efficiency of functions. They are:
a += b equivalent to a = a+b
a -= b equivalent to a = a-b
a *= b equivalent to a = a*b
a /= b equivalent to a = a/b
It is faster to use these operators rather than the explicit operations.
C: The ++ and -- operators are not included, since the evaluation method
C: cannot handle pre- and post- operations rigorously.
Relational operators
--------------------
These operators allow comparisons between two values. Of course, the set of
complex numbers has no logical ordering; all relational comparisons are
done with real parts only, but equivalence comparisons are done on both
real and imaginary parts. This system allows for people who are writing
real-number applications to once again completely ignore complex numbers.
To use relational operators on imaginary parts, you must use Im().
Relational operators are used in BOOLEAN expressions; these expressions can
take one of two values: TRUE (1.0) and FALSE (0.0). In the table below, a and
b represent numerical expressions, and E and F represent boolean expressions.
BOOLEAN EXPRESSION TRUE if and only if
a == b a has same value as b
a != b a has different value to b
a > b value of a greater than that of b
a >= b value of a greater than or equal to that of b
a < b value of a less than that of b
a <= b value of a less than or equal to that of b
E && F E is TRUE and F is TRUE
E || F E is TRUE or F is TRUE
Examples:
5 > 3 TRUE
2 != 2 FALSE
2 + 10i >= 20 + i FALSE (imaginary parts are ignored)
3 > 2i TRUE (2i has real part 0.0)
-3 > 2i FALSE
and as always, consider rounding errors when comparing:
PI == atan(1)*4 FALSE
The logical operators && and || use short-circuit evaluation. This means
that they only evaluate their right-hand argument if the result of the
operation is still undetermined. An example will clarify.
(5 > 3) && (6 < 8) Needs to evaluate E and F
E F
(5 > 8) && (6 < 8) E evaluates to FALSE, so expression is false,
E F and so F is not evaluated.
This has some important implications, for example if (boolean) expression F
contained an assignment expression, then it might not be evaluated
depending on expression E.
I have found that short-circuit evaluation is the most convenient way of
handling && and ||. Some languages don't guarantee even order of evaluation
(e.g. Pascal) and this can sometimes create harder to read programs.
One other operator is present in icalc, as a shorthand for if-then-else
statements, the ternary operator, '?:'. This has the form
E ? a : b
and reads as "if E is true then the value of the ternary operator is a,
otherwise it's b". Only one of a or b is evaluated. An example will clarify:
(x >= 2) ? (x -= 1) : (x = 100)
means "If x is greater than 2, then subtract one from x, otherwise assign the
value 100 to x".
Numerical expressions can stand in for boolean expressions. A numerical
expression has boolean value TRUE if its real part is non-zero, and FALSE
if it is zero. So, for example, if x is purely real,
y = (x ? 1 : 0) (*)
is equivalent to
y = (x != 0 ? 1 : 0) (**)
and assigns 1 to y if x is non-zero, and 0 to y if x is zero.
Do remember that imaginary parts are ignored in comparisons. In x were
purely imaginary, (*) and (**) would assign 1 and 0 respectively to y (i.e.
they would behave differently).
Arrays
------
icalc has simple one-dimensional arrays, which can only be declared outside
function definitions. Array elements are referenced using square brackets,
and the first element has index 1.
C: This is in contrast to C, where indices start at zero. The System used in
C: icalc is more suited to many mathematical applications, although I may
C: add a facility to choose starting index in future.
To create an array a, use the statement
array a[dimension] # creates cleared array
The dimension should be a positive integer. It may be an expression, which
will have its imaginary part discarded, and rounded DOWN to an integer.
All elements initially hold the value zero. You may optionally pre-assign
the array at creation by using
array a[dimension] = { expr1, expr2, ... exprP }
This second form allows pre-initialization of elements 1 to P;
P must be <= dimension (so can partially initialise array).
Arrays are referenced by a[expr], the index being calculated as the
real part of <expr> rounded down to an integer (ie. floor(Re(expr))).
Range checking is performed, and user will be informed of invalid array
references.
As already mentioned, arrays must be created at the top level, ie. NOT in
function definitions. In this version, there are NO local (to function)
arrays.
There are a few builtin functions to help you manuipulate arrays:
sizeof(a) returns number of elements in a,
resize(a,n) resizes a to n elements,
display(a) prints a list of elements in a.
The latter two return a value 1 by convention. If you resize() an array to
make it larger, the new elements created will NOT be set to zero.
Here is a small example session to demonstrate some features:
> array a[3] = { 1, 2, 3 }
> display(a)
a[1] = 1
a[2] = 2
a[3] = 3
1
> a[2] = 12
12
> a[3] < a[2] ? x = 3 : x = 2
3
> sizeof(a)
3
> resize(a,4) # a[4] will contain garbage
1
> a[4] = 4
4
> display(a)
a[1] = 1
a[2] = 12
a[3] = 3
a[4] = 4
1
> resize(a,2)
1
> display(a)
a[1] = 1
a[2] = 12
Blocks
------
Blocks are a way to group expressions so they are treated as one big
expression. They have the form
{ expr1; expr2; ... exprN; }
and the value of a block is the value of its last expression, exprN. The
expressions may also be separated by newlines (as many as you like)instead.
Blocks can have as little as one expression, which is sometimes useful for
easier-to-read source code.
Examples:
> { a=3;b=2;a*b;}
6
> {
> a = 2
> b = 3
> c = 4; d = 5
> a*b*c*d
> }
120
Blocks are principally of use with control-flow constructs, and further
(more useful) examples are given below.
Control-flow constructs
-----------------------
A number of control-flow constructs are available, closely modelled on those
of the C language.
C: C is a free-form language, and spacing can appear anywhere in C source.
C: icalc however, is free-form only per-line, due to its interactive nature;
C: icalc must be able to determine when an expression stops, so that it knows
C: when to print a result. Care should be taken to follow icalc's conventions.
Where newlines are shown, they are optional; where they are not shown, they
are not permitted, and will either cause a syntax error, or incorrect
behaviour. All horizontal spacing is optional, and used for clarity.
Below, Bexpr denotes a boolean expression, Texpr is the expression
evaluated if Bexpr is TRUE, and Fexpr the expression evaluated if Bexpr is
FALSE.
if-else
-------
A construct common to almost all programming languages is the if-statement.
In icalc, it has the form
if (Bexpr)
Texpr
or with an else-clause,
if (Bexpr)
Texpr else
Fexpr
Note that 'else' keyword must be on same line as Texpr. If it were allowed
on the next line, non-else-clause if-statements would cause problems in
interactive use. It sometimes looks ungainly, but blocks may be used to
spread it over lines, as shown below.
Some examples:
if (a == 2)
b = a+1
if (a == 2)
b = a+1 else b = a-1
if (a == 2) {
b = a+1
} else {
b = a-1
}
while-loop
----------
This is an iteration construct, of the form
while (Bexpr)
Texpr
and reads as "while the boolean expression Bexpr evaluates to TRUE, evaluate
Texpr". The following example calculates n!, where n is an integer:
f = 1; n = 10; # initialize f, n
while (n > 0) # n not yet zero, so still work to do
{
f *= n # multiply f by n
n -= 1 # decrement n
}
# f now holds value of 10!
do-while-loop
-------------
This is another iteration construct, closely related to a while-loop, with
the distinction that the loop-test (the boolean expression governing the
loop's operation) is performed AFTER the body of the loop. It has the form
do
Texpr
while (Bexpr)
Texpr is evaluated first; then, if Bexpr is TRUE, Texpr is re-evaluated and
Bexpr tested again; and so on...
Whereas with a while-loop, the Texpr may never be evaluated (if Bexpr
evaluates to FALSE initially), in a do-while-loop, Texpr is always
evaluated at least once.
An example -- sin() applied five times to a value x:
n = 5; s = x # n is number of times to apply sin()
do {
s = sin(s) # apply sin()
n -= 1 # decrement n
} while (n >= 1) # more to do?
# s has value sin(sin(sin(sin(sin(x))))) (sin 5 times)
for-loop
--------
The for-loop in icalc is like that of the C language, and has the form
for (initexpr; condexpr; incexpr)
expr
initexpr, condexpr and incexpr are all optional (but semi-colons ; are not).
The for loop is a shorthand for
initexpr
while (condexpr)
{
expr
incexpr
}
Example:
array a[100]
for (n = sizeof(a); n > 0; n -= 1)
a[n] = 1
# all elements of a are 1.
C: icalc's for-loop is not as flexible as C's; there is no comma operator,
C: and initexpr, condexpr and incexpr can only be simple (not blocks).
Function definitions
--------------------
The User Guide explains how to define simple functions. As mentioned there,
you can use a block as the body of a function (e.g. rec2pol definition).
There are more sophisticated facilities available, which provide a similar
functionality to that of C. However, for a number of reasons, the syntax
used in icalc differs widely from C's.
Function parameters may be one of four types:
VALUE: These are the normal parameters that we've been using
all along.
POINTER: These are like variable parameters in Pascal.
Denoted in parameter list by *.
ARRAY: These are passed by reference, NOT value, as in C
Denoted by @.
EXPR: Strange one this. It's like a pointer to a function,
but more/less flexible depending on how you look
at it. Example below will clarify.
Denoted by ~.
Note that, although a function cannot have local arrays, the array
reference parameter IS local, and completely independent of arrays defined
globally.
Functions may also have local variables, introduced by a statement
local v1,v2,v3,...,vN
within the function body. There can be as many local declarations as you
like. All local variables behave like VALUE variables - there are no local
pointers or arrays as yet.
C: local declarations apply to the function they appear in; there are no
C: block-local variables.
The following small suite of functions demostrate the ideas above.
# swaps values in variables a and b.
func swap(*a,*b) = { # a and b are pointers
local tmp # only expressions in swap() know about tmp
tmp = a
a = b
b = tmp
}
x = 5; y = 7
swap(x,y) # x now holds 7, y holds 5
# nexpr is an expression in (global) variable n
# a is an (arbitrary) array.
# fill evaluates Nexpr for every index n of array a, and places
# result in a[n].
func fill(@a, ~nexpr) = {
for (n = sizeof(a); n > 0; n -= 1)
a[n] = nexpr; # just referencing nexpr causes
# it to be evaluated.
}
I'm sure you will agree that these mechanisms allow many sophisticated
operations to be performed by user-functions. The examples in this document
are simple-minded for the most part, to give you the bare facts about what
can be done. I have included many sample script-files containing one or more
function definitions which are useful to a varying degree, and in some
cases add very powerful extensions to icalc.
The script files include:
array.ic - utility routines for arrays
arraytest.ic - tests routines defined in array.ic
bern.ic - computes Bernoulli numbers
cheby.ic - computes coefficients for Chebychev polynomials
gamma.ic - gamma and related functions
gl.ic - integration by 10-pt Gauss-Legendre quadrature
loop.ic - demonstrates all looping constructs
poly.ic - polynomial support routines
root.ic - root-finding of functions
sort.ic - routine to perform Shell sort on arrays
trig.ic - some rarely used trig-functions
Undocumented commands
---------------------
There is a 'clear' command, which deletes from memory a variable, array or
function. However, care MUST be taken, since icalc does not check that the
object being deleted is not referenced elsewhere (in a function). If it is
referenced after it has been deleted, the system will more than likely
crash. The command was only really added to delete large arrays which take
up a lot of memory. You have been warned...
clear <symbol>
Caveats
-------
Assignment
----------
When assigning variables within another expression, it is safer to
enclose the assignment in parentheses, to ensure that the expression
is evaluated correctly.
Defining functions in terms of other functions
----------------------------------------------
A function is stored internally as a structure, and a reference to
a function, f, from another, g, creates a pointer to the structure
for f. Thus, changing f changes g also.
An example will illuminate this:
> func f(x) = x*x
> func g(x) = f(2*x)
> f(4)
16
> g(4)
64
> func f(x) = sqrt(x) # changes behaviour of g
> f(4)
2
> g(4)
2.82842712
Therefore, you cannot use function definitions as a kind of function
assignment.
Comparisons
-----------
Due to the inexactness of computer calculations, you must be careful
when using comparison operators. Again, an example will illuminate:
> j = 12*PI
37.69911184
> while j != 0 do [print(j), j = j-PI]
37.69911184
34.55751919
:
3.14159265
-3.5527136e-15 <--- not quite zero
-3.14159265
:
etc.
The arrow points to the position where, because of rounding errors,
j is not exactly zero, and the loop never terminates, since the
condition j != 0 is always satisfied.
There are two ways to get round problems like this. You can use a >=
operator, or, if comparison is with (theoretically) integral values,
the int() function, which rounds its argument to the NEAREST integer.
Questions
---------
I've tried to cover everything in a clear and precise way, but almost
certainly have failed in some areas. If you have any questions about icalc,
write to me and I'll respond as soon as I can. Addresses are in the User
Guide.
Also, if you write any scripts which you feel other users may find useful
(for example, routines applying to physics or astronomy calculations)
please send them to me for inclusion with the next release of icalc.
Enjoy,
Martin Scott.