From uwm.edu!cs.utexas.edu!swrinde!emory!sol.ctr.columbia.edu!usenet.ucs.indiana.edu!nickel.ucs.indiana.edu!sthiagar Wed Jun  9 11:51:12 CDT 1993
Article: 4121 of comp.lang.rexx
Newsgroups: comp.lang.rexx
Path: uwm.edu!cs.utexas.edu!swrinde!emory!sol.ctr.columbia.edu!usenet.ucs.indiana.edu!nickel.ucs.indiana.edu!sthiagar
From: sthiagar@nickel.ucs.indiana.edu (sivasailam thiagarajan)
Subject: Six Rules of Thumb for Beginning REXX Programmers (long)
Message-ID: <C88DGu.91G@usenet.ucs.indiana.edu>
Sender: news@usenet.ucs.indiana.edu (USENET News System)
Nntp-Posting-Host: nickel.ucs.indiana.edu
Organization: Indiana University
Date: Mon, 7 Jun 1993 02:55:41 GMT
Lines: 572

SIX RULES OF THUMB FOR BEGINNING REXX PROGRAMMERS
(who already know Pascal or C)

(Copyright (c) 1993 by Raja Thiagarajan. This file may be copied for
any NONcommercial purpose as long as it is left unchanged.)

Since there are plenty of books, articles, and even OS/2 help files
that teach REXX, why do we need this article? Because those books,
articles, etc. are mostly aimed at people who have *never* programmed
before. Thus, they have two major drawbacks. First, they dwell on
things that programmers already know, like what a variable is. Second,
they don't warn would-be REXX programmers that some things are
*different* in REXX, and some of the "habits" that you might carry over
from other languages can get you in trouble.

This article gives six "rules of thumb" that should help you get
started in REXX if you already know C or Pascal (or, really, any of the
children of Algol).

START EVERY REXX PROGRAM WITH A COMMENT!

You already know this. There shouldn't be any reason for me to repeat
it ... except that it's an easy mistake to make, and it ties in with an
important concept that we'll get to under the second rule.

Here's "hello, world" in REXX:

	/* hello.cmd -- program to say hi*/
	say "Hello, world"

Type these two lines in your favorite editor, save the result as
HELLO.CMD, and enter HELLO at an OS/2 prompt. Your computer should
respond "Hello, world".

Under OS/2, you are *required* to start *every* REXX program with a
comment. And I *do* mean *start*; if there's so much as a space or
carriage return in front of that comment--well, why don't you try that
now? Insert a space in front of the comment and try running the program
again. You'll see a big fat error message. So: START EVERY REXX PROGRAM
WITH A COMMENT.

(By the way, the conventions for comments in REXX are simple. Each
comment begins with a "/*" and ends with a "*/". Comments can be spread
over several lines. Unlike ANSI C or Pascal, REXX permits you to nest
comments, so it's *much* easier to comment out hunks of code for
testing.)

BE CAREFUL ABOUT USING SPACES IN CERTAIN PLACES!

You've already had a taste of this in the previous rule: If you put a
space before the opening comment, your program will bomb. But there are
two more places where the significance of spaces will trip up someone
who's used to programming in C or Pascal. In REXX, everything is
ultimately a string. If you want to concatenate two strings, you can do
so by putting the explicit string abuttal operator ("||") between them.
You can *also* concatenate them by simply writing one after another
(this is known as "implied abuttal"). Please enter and run the
following program.

	/* hello2.cmd -- Another program to say hi*/
	greeting = "Hello"
	say greeting "world"
	say greeting"world"

You should see "Hello world" on one line, followed by "Helloworld" on
the second line. Your first guess might be that SAY in REXX is like
PRINTF in C or WRITELN in Pascal; that it can accept an arbitrary
number of arguments. But you'd be wrong. SAY can only be followed by a
*single* expression: the string to be displayed. However, since
greeting holds a string and "world" is a string, and since you've
written one after the other, REXX treats this as implied abuttal. That
is, REXX concatenates the two strings to form a *single* string which
it then passes to SAY. Thus, the third line in the program above is
equivalent to

	say "Hello" "world"

or
	say "Hello world"

On the other hand, the last line of the program is equivalent to

	say "Hello"'world'

(In REXX, strings can be delimited by either single or double quotes.
As in Pascal, you can "double" the delimiter to make it appear in a
string literal. If I hadn't switched delimiters, the string would have
been the one displayed as Hello"world.) Since there is no space between
the strings, REXX abuts the strings *without* inserting a space between
them. Thus, this line is equivalent to

	say "Helloworld"

and it displays what you saw when you ran the program.

Going back to the third line: You typed it with a single space between
the variable 'greeting' and the string "world". What if you had used
*two* spaces? Try it now. Edit the program and re-run it.

Surprised? In REXX, more than one space is always treated the same as a
single space (except in string literals). This is why you can write

	if greeting = 'hello' then
	   say "It's hello"

instead of having to always write

	if greeting = 'hello' then
	say "It's hello"

Some of you are starting to yawn. So spaces are significant when you do
string concatenation; so what? How often do you *do* string
concatenation? Well, in REXX, you do it *a lot*. After all, it's the
easiest way to output more than one thing at a time using the
"preferred" output instruction! But there's a serious implication here
that will cause you no end of grief until you figure it out. It's
*only* a problem for people who have learned another language like C or
Pascal and who expect REXX will work the same way. Up above, I wrote
"You can *also* concatenate [strings] by simply writing one after
another." Do you see a problem there?

No? Enter the following program:

	/* func.cmd -- program to demonstrate a "problem" with
	               function calls*/
	say abs(-3) "is *not*" abs (-3)

Before you run it, try to predict what it will display. Now run it.

Well, whatever you predicted, it probably wasn't the

	3 is *not* ABS -3

that you actually got. What happened?

The "problem" is this. When you write abs(-3), REXX interprets it as a
call to the absolute value function with -3 as an argument. However,
when you put a space in front of the parentheses, REXX interprets this
as an implicit abuttal; it assumes that you want to concatenate the
string value of the variable 'abs' with the string '-3'. (The default
value of 'abs' is "ABS" since the default value of *every* REXX
variable is a string consisting of its name written in uppercase. By
the way, like Pascal but unlike C, REXX ignores the upper/lower-case
distinction outside of string literals.) Of course, since there's a
space between the elements, you get a space between "ABS" and "-3".

Some of you will recoil in horror at this: How dare REXX mandate that
you *cannot* put a space between a function's name and its arguments!
For my own part, I can only say that it's reasonable when you get used
to it. And if REXX had been the first language you'd learned, it
probably would seem weird the other way. So, grin and bear it, and BE
CAREFUL ABOUT USING SPACES IN CERTAIN PLACES.

An important digression: These first two rules of thumb bring up a very
important difference between REXX and Pascal, C, or many other
programming languages. Namely: *REXX* *is* *not* *entirely* *a*
*free-form* *language*. Sometimes, you have to be careful about where
you put spaces in REXX. And sometimes you have to be careful about
where you split lines. For instance, while Pascal or C have no trouble
if you put arguments to a procedure on a different line from the name
of the procedure, REXX is *not* as forgiving. Using SAY as an example,

	say
	"Hello world"

is *not* the same as

	say "Hello world"

The latter displays "Hello world"; the former displays a blank line and
then bombs when it tries to execute "Hello world" as an OS/2 command.
If you need to continue a clause across a line break, use a comma. The
comma will be replaced by a space and then REXX will continue as if the
end-of-line character didn't exist. So, for instance,

	say ,
	"Hello world"

*will* display "Hello world".

Enough digression, and on to the third rule of thumb, which is

DON'T CONFUSE SUBROUTINE CALLS WITH FUNCTION CALLS!

In C or Pascal, you use pretty much the same syntax whether you're
calling a function (ie, a routine that returns a value) or a procedure
(a routine that *doesn't* return a value). In fact, C blurs the
distinction by letting you "ignore" the return value of a function if
you want. But in REXX, the way you call a subroutine (which is REXX's
term for a procedure) is *completely* different from the way you call a
function. Here's an example of a function call in REXX:

	length = max(x_coord, y_coord)

and here's an example of a subroutine call:

	call charout myFile, dist

In REXX, a function is called by giving its name, followed *directly*
by the left parenthesis (no space permitted! Remember the second
rule!), followed by zero or more arguments (separated by commas) and
then the right parenthesis.

By contrast, a subroutine is called by using the keyword CALL, followed
by the name of the subroutine, followed by a list of zero or more
arguments separated by commas. No parentheses are used.

(Digression: Am I the only one who's noticed that Microsoft's latest
Visual Basic products seem to have picked up this distinction *and*
syntax from REXX?)

Having said all that, let me confess that you *can* call functions as
if they were subroutines. In this case, the return value goes into the
special variable RESULT. For instance:

	/* subr.cmd -- Calls a function like a subroutine */
	call abs -3
	say "abs(-3) is" result

will display "abs(-3) is 3". Calling a subroutine as if it were a
function is an exercise in futility. Thus, *don't* do something like

	charout(myFile, "foo")	/*WRONG!*/

And always remember: DON'T CONFUSE SUBROUTINE CALLS WITH FUNCTION
CALLS!

On to the next rule of thumb, namely:

DON'T TREAT REXX COMPOUND VARIABLES AS ARRAYS!

In Pascal and C, you declare arrays and reference their elements using
brackets ("[]"). In REXX, you *don't* declare arrays for two good
reasons: First, you can't declare variables, and second, REXX doesn't
*have* arrays. Okay, more seriously: REXX's compound variables can
*often* be used as arrays. For instance, try the following program:

	/* comp1.cmd -- 1st program to try compound vars */
	do j = 1 to 3
	   square.j = j * j
	end j

	do i = 1 to 3
	   say "square."i "is" square.i
	end i

You will get "square.1 is 1", "square.2 is 4", and "square.3 is 9". In
this case, we could treat the square compound variable as if it were an
array. But in other cases, we can't. For instance, try deleting the
last three lines in the above program and replace them with the
following:

	do i = 2 to 4
	   say "square."i-1 "is" square.i-1
	end i

If the dots in REXX worked like brackets in C or Pascal, this program
should display the same thing as before, right? Well try it.

You got "square.1 is 3", "square.2 is 8", and then a "Bad arithmetic"
error message. What happened? Well, "square.i-1" does *not* mean
"calculate i-1 and then reference that element of the square array". It
more nearly means, "calculate i, reference that element of the square
array, and subtract 1 from it." (The "bad arithmetic" occurs when you
try to subtract 1 from the value of square.4, which is "SQUARE.4".)
Actually, it doesn't even mean that, because REXX doesn't *have*
arrays--oh, I said that before.

"Aha!" you say. "There's an easy way to fix this! We can use
parentheses and write

	do i = 2 to 4
	   say "square."i-1 "is" square.(i-1)
	end i

and *that* will work!" Uh huh. Try it now.

This time, the program bombs *immediately*. The problem is,
"square.(i-1)" means "Call the function named 'square.' with the
argument 'i-1'." Since you haven't declared a function named 'square.',
REXX complains that the "Routine was not found."

(Hold it! Do *not* tell me that we can fix this by putting a space in
front of the parentheses! It'd break my heart to find out that you
slept through the second rule. ;-)

How can we fix this? How can we easily reference an array element in
REXX using an arbitrary expression? Simple enough: We *can't*. If you
want to reference a compound variable using some arbitrary expression,
you should set a variable to that expression and then use the variable.
So, for instance, replacing those three lines with

	do i = 2 to 4
	   j = i - 1
	   say "square."j "is" square.j
	end i

will do the trick.

In case some of you may *still* be holding out hopes that you can think
of compound variables in REXX as arrays in C or Pascal, let's try
running another program. Type in and run the following:

	/* comp2.cmd -- 2nd program to try compound vars */
	j = 3
	k = 4
	ary.j.k = "one"
	m = 34/10
	say ary.3.4 "is the same as" ary.j.k "is the same as" ary.m

If you're going to convince me that REXX compound variables are the
same as arrays in C or Pascal, you'll have to explain how the program
above works. Any volunteers?

			*	*	*

Actually, the fact that REXX compound variables are not the same as
arrays is a great *opportunity*, not a difficulty. Try the program
below:

	/* comp3.cmd -- 3rd use of compound variables */
	count. = 0
	textWords = ''
	say "Enter a line of text below:"
	parse upper pull textLine
	do i = 1 to words(textLine)
	   w = word(textLine, i)
	   count.w = count.w + 1
	   if count.w = 1 then
	      textWords = textWords w
	end i

	do i = 1 to words(textWords)
	   w = word(textWords, i)
	   say 'You used the word "'w'"' count.w 'time(s).'
	end i

Whew! Okay, now run this program. Enter "The quick brown fox jumps over
the lazy dog". Your computer will tell you each word that appeared in
the line of text, and how many times each word appeared. As far as
*this* rule of thumb goes, there are two important things to point out
in this program:

First, there is the line "count. = 0". This shows the use of a *stem*
variable in REXX. When you initialize a stem variable, *any* variable
that begins with that stem is set to that value. So, after this line,
the variables count., count.1, count.57, count.foo, and count.dracula
all have a value of zero. (Also, you can use the DROP instruction on
a stem variable to return all those compound variables to their initial
state.)

Second: Loosely speaking, we're turning count. into an "array". But
what are the indexes? The REXX function "words" returns the number of
blank-delimited words in a string, and "word(str, j)" returns the j-th
such word in str. So w is a word -- and the indexes on count are the
actual words that appear in the string! So, for instance, count."the"
holds the number of times that "the" appears in the string -- for our
sample run, that was 2.

So it's important that you DON'T TREAT REXX COMPOUND VARIABLES AS
ARAYS, because it can get you into trouble, and because you'll miss the
chance to write some *very* powerful and flexible programs.

			*	*	*

MAKE THE MOST OF THE EXTERNAL DATA QUEUE!

Enter the following program:

	/* sayhi.cmd -- program to say hello */
	say "Please enter your name below:"
	parse pull name
	if name = "" then
	   name = "Stranger"
	say "Hello," name"."

and run it, entering your name. Your computer will say hello to you.
Okay, this isn't very earth-shattering. But I want to draw your
attention to the third line of the program: "parse pull name". This is
the line that accepts input from the keyboard. *However*, that's not
its "primary" use. The PARSE PULL instruction removes a line from the
external data queue, falling back on standard input only if the queue
is empty.

To demonstrate this, enter and run the following program:

	/* push1.cmd -- program to put an item in the external data queue */
	push "Jean-luc"

and run it. What happened?

Okay, so nothing happened. Get a directory, clear the screen, and do
some other housekeeping tasks. *Now* enter "sayhi" at the prompt.

If *that* doesn't impress you, nothing will. REXX's external data queue
can be used to communicate between far-flung programs, and it isn't
affected by any intervening non-REXX programs. It's also useful
*within* a program; one subroutine can stuff as much into the external
data queue as it wants, and another subroutine can get it back out by
using PARSE PULL.

You can put things in the external data queue using PUSH or QUEUE. PUSH
puts them at the front of the queue, and QUEUE puts them at the back.
(That is, PUSH treats the queue like a LIFO stack, and QUEUE treats it
as a FIFO queue.) You take elements off the front of the queue by using
PARSE PULL. Finally, you use the REXX function QUEUED to find the
number of items that are in the external data queue. So, one quick way
to flush the queue would be

	/* flush.cmd -- Program to flush the external data queue*/
	do queued()
	   parse pull dummy
	end

(If you want to see what you're flushing, insert a "say dummy" line
after the parse pull line.) By now, I hope I've convinced you to MAKE
THE MOST OF THE EXTERNAL DATA QUEUE.

Okay, time for the sixth and last rule of thumb:

USE PARSE TEMPLATES!

Type in this sample program:

	/* temp1.cmd -- 1st program to use template */
	parse arg i j k
	say 'i is "'i'", j is "'j'", and k is "'k'"'

and run it by entering

	temp1 Alfa Bravo Charlie Delta

at an OS/2 prompt. Your computer will respond

	i is "Alfa", j is "Bravo", and k is "Charlie Delta"

Let's analyze the second line in detail. When OS/2 calls this program,
everything after the "temp1 " on the command line will be packed into a
string. The PARSE ARG instruction is used to "split up" this string and
put pieces of it into the variables you specify. In this case, you used
a parse template of "i j k", which means to put the first word from the
argument string into i, put the second word into j, and put the rest
into k. The spaces that separate these words are discarded, though any
trailing spaces *are* saved and put in the last variable. (As in
comp3.cmd, a "word" is any sequence of non-space characters that is
delimited by one or more spaces.) If this program is passed more than
three words (as in our example), the extras all become part of k. If
*fewer* than three words are given, then the "extra" variables are set
to the empty string. Try entering "temp1 hello world" to see the latter
rule in action.

If you're a C programmer, you may already see the great convenience of
parse templates over argc and argv[]; certainly it's nice that the
arguments go into the variables *you* want without having to do lots of
calls to strcpy. But wait, there's more! For one thing, PARSE ARG is an
*instruction*, not a declaration. Add the following two lines to the end
of temp1.cmd:

	parse arg m
	say 'm is "'m'"'

and rerun it with "temp1 Alfa Bravo Charlie Delta". You get the same
results as above, *and* the computer will also inform you that 'm is
"Alfa Bravo Charlie Delta"'. So if you "change your mind" about what
values should go where, or if you want to use the values in several
different ways, you can do it easily.

But wait, there's still more! Suppose you're only interested in the
first and third words given to your program. You can use a period as a
placeholder in the template. Add the following lines to temp1:

	parse arg w1 . w3 .
	say 'w1 is "'w1'" and w3 is "'w3'"'

The top line means, "Put the first word in w1, discard the second word,
put the third word in w3, and discard the rest of the line." You should
be able to easily predict what will happen when you enter "temp1 Alfa
Bravo Charlie Delta". Try it to check if you're right.

If *that* isn't enough, you can also use column numbers in parse
templates. Try adding these lines to your (now quite large!) temp1.cmd:

	parse arg c1 5 c5 10
	say 'c1 is "'c1'" and c5 is "'c5'"'

and run it via "temp1 Alfa Bravo Charlie Delta". (Note the spelling of
"Alfa".) You will be told that c1 is "Alfa" and c5 is " Brav". This new
PARSE ARG line means "Put the characters in columns 1-4 into the
variable c1, put the characters in columns 5-9 into the variable c5,
and discard the characters from column 10 on (if any)." Column 1 is the
first character after the space that follows the name of your program.
Try predicting what would happen if you put an extra space in front of
the word "Alfa" when you execute temp1 with the same arguments again,
and try it to see if your prediction was correct.

(By the way, you can also use "relative positional patterns"; for
instance, the PARSE ARG line could have been written with "+5" instead
of "10".)

Have we exhausted the possibilities of the parse template? NO! However,
I'll just show one more example. I'm sure you're sick of temp1, so
let's try a new program, namely:

	/* vcopy.cmd -- verbose copy command */
	parse upper arg "FROM: " srcFile "TO: " destFile 
	"copy" srcFile destFile

Type this program in and save it as vcopy.cmd. (Note that we've added a
new keyword, "upper". This keyword will convert everything to
uppercase, so the program will work no matter what combination of
upper- or lowercase our users type). Now run it by entering

	vcopy from: temp1.cmd to: boring.cmd

OS/2 will copy the file temp1.cmd to boring.cmd. (We put the OS/2 copy
command in quotes so REXX wouldn't mistake it for a variable name.) The
nice thing is, the following command will give the same results:

	vcopy this is ignored from: temp1.cmd to: boring.cmd

To summarize, you can use string literals in a parse template and REXX
will do all the hard work of matching the arguments to your template.
Clearly you should USE PARSE TEMPLATES because they'll make your job
much easier.

(Here's an exercise for the reader: Modify vcopy.cmd so that if the
user types more than one word after "FROM: " or "TO: ", the extra words
are ignored. Test your modified program by entering

	vcopy from: temp1.cmd through OS/2 to: boring.cmd at long last

and seeing that it *still* copies temp1.cmd to boring.cmd. Hint: You
only need to add two characters!)

WHERE TO GO FROM HERE

If you're serious about REXX, I highly recommend that you get Michael
F. Cowlishaw's _The REXX Language_. _TRL_ is *the* standard book that
defines REXX; it's like "K&R" is to C. I found the book to be a joy to
read because Cowlishaw explains much of the *why* behind the features
of REXX, and because he has a good sense of humor that he *never* uses
to put down other computer languages (or the people who program in
them). My only complaint about the book is the price; does
Prentice-Hall *really* have to charge $38 for a 200-page paperback?

The full title is _The REXX Language: A Practical Approach to
Programming, Second Edition_. It's published by Prentice Hall, and the
ISBN number is 0-13-780651-5.

Another book I found quite helpful is Charles Daney's _Programming in
REXX_, published by McGraw-Hill, Inc. and with ISBN 0-07-015305-1.
Though not as concise (and sometimes not as clear) as _TRL_, it
provides *many* interesting sample programs and a thorough discussion
of various time-saving REXX tips and tricks. I really liked the
discussion on writing filters in REXX (page 187 on). Daney's book is
$44.95.

There are several newsgroups and mailing lists that include much useful
information. If you have access to the Internet, I highly recommend
that you check out the comp.lang.rexx newsgroup. The people on c.l.r
are among the friendliest I've "met" on the Internet. For matters
specific to OS/2 REXX, it's also worth reading comp.os.os2.misc and
comp.os.os2.programmer. If you have access to BITNet, check out the
REXXLIST discussion list.

Finally, comp.lang.rexx has a FAQ maintained by Eric Giguerre. You can
obtain it as the file rexxfaq in the pub/ directory on the
rexx.uwaterloo.ca ftp site. This FAQ includes a bibliography of REXX
books, detailed information on the BITnet lists, ftp sites for
REXX-related information, and much more.

Raja Thiagarajan / sthiagar@bronze.ucs.indiana.edu / 6-6-93


From uwm.edu!cs.utexas.edu!usc!elroy.jpl.nasa.gov!decwrl!uunet!csfb1!jbrock Fri Jun 18 11:00:46 CDT 1993
Article: 4296 of comp.lang.rexx
Newsgroups: comp.lang.rexx
Path: uwm.edu!cs.utexas.edu!usc!elroy.jpl.nasa.gov!decwrl!uunet!csfb1!jbrock
From: jbrock@csfb1.uucp (John Brock)
Subject: RXMATH math package for REXX
Message-ID: <C8sptG.9s9@csfb1.fir.fbc.com>
Sender: news@csfb1.fir.fbc.com (Usenet News <Jim Freeman>)
Organization: First Boston Corporation
References: <C8oHnC.DMz@csfb1.fir.fbc.com> <1993Jun16.130000.17987@sernews.raleigh.ibm.com> <C8spKn.9qK@csfb1.fir.fbc.com>
Date: Fri, 18 Jun 1993 02:34:27 GMT
Lines: 1038


I have received a number of requests for copies of my RXMATH math
package for REXX, so I have decided to repost it.  It was originally
posted to comp.lang.rexx in October 1992.  This version is functionally
identical to the old one, but the code has been changed to make it more
SAA compliant (for the most part I just renamed a lot of variables to
get rid of the previously ubiquitous '$' character).

To summarize, RXMATH is an infinite precision math package, written
entirely in REXX, containing all the usual pocket calculator math
functions plus an interpretive/interactive "calc" function.  It is
reasonably efficient and extremely accurate.

As long as I'm posting I will take the opportunity to make another plea
for built-in REXX math functions.  Please?  Please please please!!!
Math functions are well defined, there is a standard set that everyone
expects, and they really aren't that hard to do, even with infinite
precision.  I can't see any reason to leave them out of the language!
Mike, are you listening?

I would also like to advocate the kind of format I use in RXMATH as a
standard.  I think it is better to put all the advanced math functions
into a single procedure than to have one procedure for each function.
That is, instead of having

   sin(1.5)
   ln(1.5)
   pow(1.5, 2.1)

I would have

   math('sin', 1.5)
   math('ln', 1.5)
   math('pow', 1.5, 2.1)

This is mainly an esthetic preference, but it's easily extensible and
seems better than proliferating two dozen new built-in functions.


-------- Beginning of RXMATH math package for REXX ----------------------

This package contains three files:  1) RXMATH EXEC
                                    2) RXMATHI EXEC
                                    3) RXMATH HELPREXX

The first two files are REXX execs; the third is a CMS style help
file.  The execs were developed under CMS, and are meant to be SAA
compliant, so they should run with minimum modification under CMS, TSO,
and OS/2.  Please note that there are some procedures at the end of
each of the two execs which you may want or need to delete (just take a
look -- it's self explanatory).  My installation's CMS level is VM/XA
CMS 5.6, and my REXX version is REXX370 3.40.

RXMATH is the main exec.  It handles all calculations, and may be
compiled.  RXMATHI is called by RXMATH.  It exists only to support the
"calc" function, and may not be compiled, as it contains "interpret"
statements.

RXMATH provides arbitrary precision math functions, and must be run
under a version of REXX which supports arbitrary precision arithmetic.
RXMATH and RXMATHI use the REXX stack to communicate with each other,
so this is required if you are going to use the "calc" function.

RXMATH is offered as unsupported freeware.  I only ask, if you modify
it, that you leave my name in it somewhere.

And while I won't promise any kind of support, I would certainly be
inclined to fix any bugs that were pointed out to me, or answer any
questions.  I can currently be reached at the addresses below.


John Brock -- June 17, 1993

uunet!csfb1!jbrock
jbrock@csfb1.fir.fbc.com


-------- Cut here -------- Beginning of RXMATH EXEC ---------------------
/* REXX -- Written by John Brock, 09/08/89, SAA version 10/15/92.
 
RXMATH:     Arbitrary precision mathematical functions for REXX
 
Syntax:     Command:        EXEC RXMATH <precision> function <x <y>>
            REXX function:  RXMATH(<precision,> function <,x <,y>>)
 
Where:      precision  is the desired REXX precision of the result
                         (if omitted or zero precision defaults to 9)
            function   is the name of a mathematical function
            x, y       are arguments to the function
 
Functions:  FACT(x)    --  Factorial of x
            PERM(x,y)  --  Permutations of x by y
            COMB(x,y)  --  Combinations of x by y
            SQRT(x)    --  Square root of x
            POW(x,y)   --  x to the power of y
            LOG(x,y)   --  Log of y base x
            EXP(x)     --  e to the power of x
            LN(x)      --  Natural log of x
            PI()       --  Value of pi
            SIN(x)     --  Sine of x
            COS(x)     --  Cosine of x
            TAN(x)     --  Tangent of x
            COT(x)     --  Cotangent of x
            SEC(x)     --  Secant of x
            CSC(x)     --  Cosecant of x
            ARCSIN(x)  --  Inverse sine of x
            ARCCOS(x)  --  Inverse cosine of x
            ARCTAN(x)  --  Inverse tangent of x
            ARCCOT(x)  --  Inverse cotangent of x
            ARCSEC(x)  --  Inverse secant of x
            ARCCSC(x)  --  Inverse cosecant of x
            CALC(x)    --  Calculate the value of an expression
                             (e.g. '1 + exp(pi() / 2)')
 
Notes:      All angles for the trigonometric functions are in radians.
            Calling RXMATH as a command without arguments prints help.
            When called as a command result is printed to screen,
              and on error RXMATH returns 1.
            When called as a function result is returned to caller,
              and on error RXMATH exits without returning a value.
            When the CALC function is called without an argument
              RXMATH goes into an interpretive REXX calculator mode
              (change or drop REXX variable "prompt" to alter prompt).
 
*/
 
signal on halt /* First instruction, because we could halt anytime! */
signal on syntax /* Mainly in case we hit internal numeric limits. */
signal on novalue /* Shouldn't ever happen. */
 
parse source . cmd? pname . /* Get program name. */
cmd? = cmd? = "COMMAND" /* Were we called as a command? */
dgt = digits() /* Default for precision argument. */
glb.0dgt = dgt /* (Save it). */
extra_dgt? = 1 /* Increase internal precision above requested value? */
 
/* Get precision, function name, and arguments from argument list. */
/* Note that the "extra_dgt?" variable may be taken from argument */
/* list.  This is an "undocumented interface", which is used to    */
/* avoid a second and redundant increase in internal precision     */
/* when RXMATH calls itself recursively through the CALC function. */
if cmd? /* Do basic argument parsing. */
  then parse arg func x y . 1 . str /* (str is for CALC). */
  else parse arg func 1 _extra_dgt? _dgt, x 1 str, y
select /* Deal with possible non-basic argument parsing. */
  when cmd?
    then if datatype(func, "N") /* First argument is precision. */
      then parse arg dgt func x y . 1 . . str
      else if func = ""
        then signal help /* No arguments, so print help message. */
  when (_extra_dgt? == 0 | _extra_dgt? == 1) & datatype(_dgt, "N")
    then parse arg extra_dgt? dgt, func, x 1 str, y
  otherwise if datatype(func, "N") /* First argument is precision. */
    then parse arg dgt, func, x 1 str, y
  end
func = space(translate(func))
dgt = dgt / 1
 
/* Validate precision argument. */
if ^datatype(dgt, "W") | dgt < 0
  then call error "Illegal value for precision:" dgt
 
/* List of all functions supported by RXMATH (and available in CALC). */
funcs = "FACT PERM COMB SQRT POW LOG EXP LN PI SIN COS TAN",
  "COT SEC CSC ARCSIN ARCCOS ARCTAN ARCCOT ARCSEC ARCCSC CALC"
 
/* "SCAN" and "PULLSCAN" are special functions which support CALC. */
/* They are "undocumented", and not listed in the function list    */
/* (which means that they are not available as CALC functions).    */
/* Note that dgt = 0 has a special meaning for these functions.   */
if ^cmd? & (func = "SCAN" | func = "PULLSCAN") then do
  if func = "PULLSCAN" then do
    if y ^= "LIT" & queued() + lines() = 0 then say x
    parse pull x
    end
  exit scan(pname, funcs, extra_dgt?, dgt, x)
  end
 
/* Validate function name. */
if func = "" then call error "Function name is missing."
if words(func) ^= 1 | wordpos(func, funcs) = 0
  then call error "Invalid function name:" func
glb.0func = func /* Save function name for use in error messages. */
 
/* If precision is zero then use default value. */
if dgt = 0 then dgt = glb.0dgt
 
/* Remove excess precision from arguments. */
numeric digits dgt
if datatype(x, "N")
  then x = x / 1
  else x = space(x)
if datatype(y, "N")
  then y = y / 1
  else y = space(y)
 
numeric digits intdgt(dgt, extra_dgt?) /* Set internal precision. */
 
select /* Call appropriate function. */
  when func == "FACT" then z = fact(x)
  when func == "PERM" then z = perm(x, y)
  when func == "COMB" then z = comb(x, y)
  when func == "SQRT" then z = sqrt(x)
  when func == "POW" then z = pow(x, y)
  when func == "LOG" then z = log(x, y)
  when func == "EXP" then z = exp(x)
  when func == "LN" then z = ln(x)
  when func == "PI" then z = pi()
  when func == "SIN" then z = sin(x)
  when func == "COS" then z = cos(x)
  when func == "TAN" then z = tan(x)
  when func == "COT" then z = cot(x)
  when func == "SEC" then z = sec(x)
  when func == "CSC" then z = csc(x)
  when func == "ARCSIN" then z = arcsin(x)
  when func == "ARCCOS" then z = arccos(x)
  when func == "ARCTAN" then z = arctan(x)
  when func == "ARCCOT" then z = arccot(x)
  when func == "ARCSEC" then z = arcsec(x)
  when func == "ARCCSC" then z = arccsc(x)
  /* The interpretive CALC function is supported by RXMATHI,      */
  /* leaving RXMATH eligible for REXX compilation (if available). */
  when func == "CALC" then do
    call "RXMATHI" str, extra_dgt? & str = "",,
      intdgt(dgt, extra_dgt? & str ^= ""), errmsg(),,
      pname "interactive mode -- enter any valid REXX instruction:"
    if symbol("result") = "VAR" /* Process output from CALC function. */
      then if str = "" /* If we were in interactive CALC mode... */
        then exit result /* ...then return result immediately. */
        else z = result /* Process non-interactive result further. */
      else if str = ""
        then exit /* A simple exit is OK from interactive mode... */
        else call error /* ...but otherwise means there was an error. */
    end
  otherwise call error "Function name is valid but not implemented."
  end
 
numeric digits dgt /* Reset precision to requested value. */
z = z / 1 /* Format result, removing trailing zeros. */
 
/* All done, so return result to user! */
if ^cmd? then exit z
say z
exit
 
 
/* Given a string, scan it for function calls that can be executed */
/* by RXMATH, and replace them with direct calls to RXMATH.  For   */
/* example, "exp(2)" might become "RXMATH(0 digits(), 'EXP', 2)".  */
/* Note that the calls to RXMATH that are constructed here supply  */
/* extra_dgt? as part of the first argument.  This is really the  */
/* only place where this should ever happen.                       */
scan: procedure expose glb.
parse arg pname, funcs, extra_dgt?, dgt, str
if dgt = 0 then dgt = "digits()"
 
do i = 1 to words(funcs) /* Loop thru list of supported functions. */
  func = translate(word(funcs, i)) /* Select a function. */
  pos = 0 /* Start at the beginning of the string. */
  do forever /* Look for multiple occurrences. */
    prepstr = prepstr(str, max(1, pos), '"' "'", '"' "'")
    pos = pos(func"(", prepstr, pos + 1) /* Needn't find ")". */
    if pos = 0 then leave /* Function not found in string. */
    if pos > 1
      then if symbol(substr(prepstr, pos - 1, 1)) ^= "BAD"
        then iterate /* (What we found was part of another function!) */
    /* Supported function found, so replace it with call to RXMATH. */
    str = insert("'"pname"'("extra_dgt? dgt", '"func"', ",,
      delstr(str, pos, length(func) + 1), pos - 1)
    end
  end
 
return str
 
 
/* Take a string, uppercase it, and blank out any quoted       */
/* substrings (to avoid unnecessary function call expansions). */
/* Input is string, starting position, left delimiters, and    */
/* corresponding right delimiters.                             */
/* Example: "ln(2) + calc('pi()')" ==> "ln(2) + calc('    ')". */
prepstr: procedure expose glb.
arg str, start, lftdlm, rgtdlm
 
do while start <= length(str)
 
  /* Find the nearest left delimiter, if any. */
  parse value (length(str) + 1) with lftpos lft rgt
  do i = 1 to words(lftdlm)
    pos = pos(word(lftdlm, i), str, start)
    if pos > 0 & pos < lftpos then do
      lft = word(lftdlm, i)
      rgt = word(rgtdlm, i)
      lftpos = pos
      end
    end
 
  if lft = "" then leave /* No left delimiter found. */
 
  /* Blank out string between left and right delimiter (if any). */
  lftpos = lftpos + length(lft)
  rgtpos = pos(rgt, str, lftpos)
  if rgtpos = 0 then rgtpos = length(str) + 1
  str = overlay(copies(" ",  rgtpos - lftpos), str, lftpos)
  start = rgtpos + length(rgt)
  end
 
return str
 
 
/* Determine how numeric digits should be set internally.           */
/* If extra_dgt? = 1 then we double requested precision, using     */
/* the default setting of numeric digits to set a lower limit (so   */
/* that a requested precision of 2, for example, won't result in an */
/* unreasonably low internal precision of 4).  If extra_dgt? = 0   */
/* then we just use the requested precision, whatever it is.        */
intdgt: procedure expose glb.
arg dgt, extra_dgt?
if extra_dgt?
  then return 2 * max(dgt, glb.0dgt) /* Extended precision. */
  else return dgt /* Unextended precision. */
 
 
/* Find value of x!. */
fact: procedure expose glb.
 
x = arg_ok(arg(1), "W >=0")
 
y = 1
do n = 2 to x
  y = y * n
  end
 
return y
 
 
/* Find the number of permutations of x things taken y at a time. */
perm: procedure expose glb.
 
x = arg_ok(arg(1), "W >=0")
y = arg_ok(arg(2), "W >=0")
if x < y then call error "Illegal arguments:" x "<" y
 
z = 1
do n = (x - y) + 1 to x
  z = z * n
  end
 
return z
 
 
/* Find the number of combinations of x things taken y at a time. */
comb: procedure expose glb.
 
x = arg_ok(arg(1), "W >=0")
y = arg_ok(arg(2), "W >=0")
if x < y then call error "Illegal arguments:" x "<" y
 
if x - y < y then y = x - y /* Adjust y. */
 
z = 1
do n = (x - y) + 1 to x
  z = z * n
  end
 
do n = 2 to y
  z = z / n
  end
 
return z
 
 
/* Find square root using Newton's method. */
sqrt: procedure expose glb.
 
x = arg_ok(arg(1), ">=0")
 
if x = 0 then return 0
if x = 1 then return 1
 
/* Adjust number so it is between 1 and 100. */
i = ilog(100, x, 1, 100)
x = x * 100 ** -i
 
y = x / 2
old = y
do n = 1
  y = ((x / y) + y) / 2
  if y = old then leave
  old = y
  end
 
return y * 10 ** i
 
 
/* Find x to the power of y. */
pow: procedure expose glb.
 
x = arg_ok(arg(1))
y = arg_ok(arg(2))
 
if y = 0 then return 1
if datatype(y, "W") & x ^= 0 then return x ** y
if x <= 0 then do
  if x < 0 then call error,
    "Non-integer power of a negative number:" "(" || x || ") **" y
  if y < 0 then call error "Zero taken to a negative power:" y
  return 0
  end
 
/* If possible use square root function (for speed). */
if abs(y // 1) = 0.5 then return sqrt(x) ** sign(y) * x ** (y % 1)
 
return exp(y * ln(x))
 
 
/* Find the log of y base x. */
log: procedure expose glb.
return ln(arg(2)) / ln(arg_ok(arg(1), "^=1"))
 
 
/* Find e to the power of x. */
exp: procedure expose glb.
 
x = arg_ok(arg(1))
 
i = x % 1
if abs(x - i) > 0.5 then i = i + sign(x)
x = x - i /* Adjust x for quick convergence. */
 
y = sum_e(x)
if i ^= 0 then y = y * sum_e(1) ** i
 
return y
 
 
/* Find the natural log of x (using sum of -((-1)**n)*(x-1)**n/n). */
ln: procedure expose glb.
 
x = arg_ok(arg(1), ">0")
 
if x = 10 then do /* Special assist for ln(10). */
  m = "2.30258509299404568401799145468436420760110148862877"
  y = format(m)
  if y ^== m then return y
  end
 
/* Adjust x between 0.5 and 1.5 (for convergence). */
i = ilog(sum_e(1), x, 0.5, 1.5)
x = x * sum_e(1) ** -i
 
x = x - 1
f = -1
y = 0
old = y
do n = 1
  f = -f * x
  y = y + f / n
  if y = old then leave
  old = y
  end
 
return y + i
 
 
/* Find e to the power of x (using the usual series expansion).    */
/* If x = 1 then save value of e (because it may be needed again). */
sum_e: procedure expose glb.
parse arg x
 
if x = 1 then do /* Return hard coded value if precision allows. */
  if symbol("glb.0e") = "VAR" then return glb.0e
  e = "2.71828182845904523536028747135266249775724709369996"
  glb.0e = format(e)
  if glb.0e ^== e /* Does hard coded value exceed internal precision? */
    then return glb.0e
  end
 
y = 1
f = 1
old = y
do n = 1
  f = f * x / n
  y = y + f
  if y = old then leave
  old = y
  end
 
if x = 1 then glb.0e = y /* Save value of e. */
 
return y
 
 
/* Return i such that bot <= (y / (x ** i)) <= top.         */
/* Conditions are x, y, top, bot > 0, x ^= 1, top >= bot   */
/* This procedure works by repeatedly looking for factors of the  */
/* form x ** (2 ** j) which are divided out of y to move it    */
/* toward the intended range (without overshooting, if possible). */
/* Note: if x > 1 then ilog(x, y, 1, x) = trunc(log(x, y)). */
ilog: procedure expose glb.
arg x, y, bot, top
 
high = y > top
sign = 1 - 2 * (high ^= (x > 1))
 
i = 0
do while high & y > top | ^high & y < bot
 
  f = x
  do j = -1
    q = y * f ** -sign
    if j >= 0 & (high & q < top | ^high & q > bot) then leave
    f = f * f
    g = q
    end
 
  y = g
  i = i + sign * 2 ** j
  end
 
return i
 
 
/* Calculate (and save) pi, using arcsin(1/2) = pi/6. */
/* Saved or hard coded value of pi used if possible.  */
pi: procedure expose glb.
 
if symbol("glb.0pi") = "VAR" then return glb.0pi
 
pi = "3.14159265358979323846264338327950288419716939937511"
glb.0pi = format(pi)
 
if glb.0pi == pi then glb.0pi = 6 * arcsin(0.5)
 
return glb.0pi
 
 
/* Find the sine of x (usual series expansion). */
sin: procedure expose glb.
 
x = arg_ok(arg(1), "TRIG")
 
x = x // (2 * pi()) /* Adjust between pi and -pi, for convergence. */
if abs(x) > pi() then x = x - sign(x) * 2 * pi()
 
f = x
y = x
x = x ** 2
old = y
do n = 2 by 2
  f = -f * x / (n * (n + 1))
  y = y + f
  if y = old then leave
  old = y
  end
 
return y
 
 
/* Find the cosine of x (usual series expansion). */
cos: procedure expose glb.
 
x = arg_ok(arg(1), "TRIG")
 
x = x // (2 * pi()) /* Adjust between pi and -pi, for convergence. */
if abs(x) > pi() then x = x - sign(x) * 2 * pi()
 
f = 1
y = 1
x = x ** 2
old = y
do n = 2 by 2
  f = -f * x / (n * (n - 1))
  y = y + f
  if y = old then leave
  old = y
  end
 
return y
 
 
/* Find the tangent of x. */
tan: procedure expose glb.
 
x = arg_ok(arg(1))
 
y = cos(x)
if y = 0 then call error "Result is infinite."
 
return sin(x) / y
 
 
/* Find the cotangent of x. */
cot: procedure expose glb.
 
x = arg_ok(arg(1))
 
y = sin(x)
if y = 0 then call error "Result is infinite."
 
return cos(x) / y
 
 
/* Find the secant of x. */
sec: procedure expose glb.
 
x = arg_ok(arg(1))
 
y = cos(x)
if y = 0 then call error "Result is infinite."
 
return 1 / y
 
 
/* Find the cosecant of x. */
csc: procedure expose glb.
 
x = arg_ok(arg(1))
 
y = sin(x)
if y = 0 then call error "Result is infinite."
 
return 1 / y
 
 
/* Find the inverse sine of x (usual series expansion). */
arcsin: procedure expose glb.
 
x = arg_ok(arg(1), "ABS<=1")
 
/* Avoid region where series converges slowly.  (Note recursion!) */
if abs(x) >= 0.75 then return sign(x) * arccos(sqrt(1 - x ** 2))
 
f = x
y = x
x = x ** 2
old = y
do n = 2 by 2
  f = f * x * (n - 1) / n
  y = y + f / (n + 1)
  if y = old then leave
  old = y
  end
 
return y
 
 
/* Find the inverse cosine of x. */
arccos: procedure expose glb.
return pi() / 2 - arcsin(arg(1))
 
 
/* Find the inverse tangent of x. */
arctan: procedure expose glb.
 
x = arg_ok(arg(1))
 
return arcsin(x / sqrt(x ** 2 + 1))
 
 
/* Find the inverse cotangent of x. */
arccot: procedure expose glb.
 
x = arg_ok(arg(1))
 
return arccos(x / sqrt(x ** 2 + 1))
 
 
/* Find the inverse secant of x. */
arcsec: procedure expose glb.
 
x = arg_ok(arg(1), "ABS>=1")
 
return sign(x) * arccos(1 / x)
 
 
/* Find the inverse cosecant of x. */
arccsc: procedure expose glb.
 
x = arg_ok(arg(1), "ABS>=1")
 
if x > 0
  then return arcsin(1 / x)
  else return -(pi() + arcsin(1 / x))
 
 
/* Validate function arguments against a given lists of tests. */
arg_ok: procedure expose glb.
parse arg x, test_list
test_list = translate(test_list)
 
/* Argument must always exist and be a valid number. */
if x = "" then call error "Missing argument."
if ^datatype(x, "N") then call error "Illegal argument:" x
 
if wordpos("W", test_list) > 0 /* Must be whole number. */
  then if ^datatype(x, "W")
    then call error "Illegal argument:" x
if wordpos(">=0", test_list) > 0 /* Must be >= zero. */
  then if x < 0
    then call error "Argument < 0:" x
if wordpos(">0", test_list) > 0 /* Must be > zero. */
  then if x <= 0
    then call error "Argument <= 0:" x
if wordpos("^=1", test_list) > 0 /* Must not equal one. */
  then if x = 1
    then call error "Illegal argument:" x
if wordpos("ABS<=1", test_list) > 0 /* Absolute value must be <= one. */
  then if abs(x) > 1
    then call error "Argument is > 1 or < -1:" x
if wordpos("ABS>=1", test_list) > 0 /* Absolute value must be >= one. */
  then if abs(x) < 1
    then call error "Argument is < 1 and > -1:" x
 
/* To accurately calculate trigonometric functions the absolute value */
/* of the argument must be small enough that we can subtract from it  */
/* a multiple of 2 * pi and get an accurate result between pi and     */
/* -pi.  Actually the test used here is too rigorous.  In most (but   */
/* not all) cases we get a completely accurate result by using        */
/* digits() % 2 (instead of digits() % 4).  But then, who really      */
/* needs sine or cosine of very large numbers anyway?                 */
if wordpos("TRIG", test_list) > 0
  then if abs(x) >= 10 ** (digits() % 4)
    then call error "Argument is too large:" x
 
return x
 
 
halt: call error errortext(4)
 
 
syntax: call error errortext(rc)
 
 
novalue: call error "NOVALUE at line" sigl
 
 
/* Print out leading comment as help. */
help:
do i = 2 to sourceline() while sourceline(i) ^= "*/"
  say sourceline(i)
  end
exit
 
 
/* Print error message and exit. */
error: procedure expose glb.
if arg(1, "E") then say errmsg(arg(1))
parse source . cmd? .
if cmd? = "COMMAND"
  then exit 1 /* Error return code is 1 when called as a command. */
  else exit   /* If called as function caller will blow up. */
 
 
/* Create an error message. */
errmsg: procedure expose glb.
parse source . . pname .
errmsg = "Error in" pname
if symbol("glb.0func") = "VAR" then errmsg = errmsg "("glb.0func")"
errmsg = errmsg "--"
if arg(1, "E") then errmsg = errmsg arg(1)
return errmsg
 
 
/* (Delete if built-in digits() function is available). */
digits: procedure
parse numeric digits .
return digits
 
 
/* (Delete if built-in wordpos() function is available). */
wordpos: procedure
return find(arg(2), arg(1))
 
 
/* (Delete if built-in lines() function is available). */
lines: procedure
return externals()
-------- Cut here -------- End of RXMATH EXEC ---------------------------


-------- Cut here -------- Beginning of RXMATHI EXEC --------------------
/* REXX -- Written by John Brock, 06/01/90; SAA version 10/15/92.
 
RXMATHI supports and should only be called by RXMATH.
 
*/
 
signal on halt
signal on syntax
 
numeric digits arg(3)
 
if arg(1) ^= ""
  then interpret 'exit ('"RXMATH"(arg(2) arg(3), "SCAN", arg(1))") + 0"
 
if arg(5, "E") then prompt = arg(5)
 
retry: if arg(1) ^= "" then exit
 
do forever
  interpret "RXMATH"(arg(2) 0, "PULLSCAN", prompt, symbol("prompt"))
  end
 
 
halt: signal on halt
say arg(4) errortext(4)
signal retry
 
 
syntax: signal on syntax
if rc ^= 44 then say arg(4) errortext(rc)
signal retry
 
 
/* (Delete if built-in digits() function is available). */
digits: procedure
parse numeric digits .
return digits
-------- Cut here -------- End of RXMATHI EXEC --------------------------


-------- Cut here -------- Beginning of RXMATH HELPREXX -----------------
.cm RXMATH HELPRXEX -- Written by John Brock, 09/08/89, version 10/15/92.
.cs 1 on
RXMATH
 
Use the RXMATH exec to do arbitrary precision mathematics.
 
RXMATH may be called as a command from the command line, in which case
the result is printed to the screen, or it may be called as a function
returning a value to REXX.  If an error occurs RXMATH prints an error
message to the screen.  Then if called as a command it exits with a
return code of 1; otherwise it exits without returning a value.
.cs 1 off
.cs 2 on
 
When issued as a command, RXMATH has the format:
 
+--------------------------------------------------------------------+
| RXMATH   | <precision> function <x <y>>                            |
+--------------------------------------------------------------------+
 
When called as a function from REXX, RXMATH has the format:
 
+--------------------------------------------------------------------+
| RXMATH(<precision,> function <,x <,y>>)                            |
+--------------------------------------------------------------------+
.cs 2 off
.cs 3 on
 
Where:      precision  is the desired REXX precision of the result
                         (if omitted or zero precision defaults to 9)
 
            function   is the name of a mathematical function
 
            x, y       are arguments to the function
.cs 3 off
.cs 5 on
 
Functions:  FACT(x)    --  Factorial of x
            PERM(x,y)  --  Permutations of x by y
            COMB(x,y)  --  Combinations of x by y
            SQRT(x)    --  Square root of x
            POW(x,y)   --  x to the power of y
            LOG(x,y)   --  Log of y base x
            EXP(x)     --  e to the power of x
            LN(x)      --  Natural log of x
            PI()       --  Value of pi
            SIN(x)     --  Sine of x
            COS(x)     --  Cosine of x
            TAN(x)     --  Tangent of x
            COT(x)     --  Cotangent of x
            SEC(x)     --  Secant of x
            CSC(x)     --  Cosecant of x
            ARCSIN(x)  --  Inverse sine of x
            ARCCOS(x)  --  Inverse cosine of x
            ARCTAN(x)  --  Inverse tangent of x
            ARCCOT(x)  --  Inverse cotangent of x
            ARCSEC(x)  --  Inverse secant of x
            ARCCSC(x)  --  Inverse cosecant of x
            CALC(x)    --  Calculate the value of an expression
                             (e.g. '1 + exp(pi() / 2)')
 
Examples:
 
1. Find the square root of 2 to 40 places:
 
       EXEC RXMATH 40 SQRT 2  (or EXEC RXMATH 40 POW 2 0.5)
 
   result: 1.41421356237309504880168872420969807857
 
2. Find the square root of 2 to 9 places (the default):
 
       EXEC RXMATH SQRT 2
 
   result: 1.41421356
 
3. From REXX, print the base 10 log of 5 to 40 places:
 
       say "RXMATH"(40, "LOG", 10, 5)
 
   result: 0.6989700043360188047862611052755069732318
 
4. From REXX, print the base 10 log of 5 to 9 places:
 
       say "RXMATH"("LOG", 10, 5)
 
   result: 0.698970004
 
5. From REXX, print 1 plus e to the power of pi/2, to 50 places:
 
       say "RXMATH"(50, "CALC", "1 + exp(pi() / 2)")
 
   result: 5.8104773809653516554730356667038331263901708746645
 
6. Enter interactive REXX calculator mode:
 
       EXEC RXMATH CALC
 
   result: RXMATH interactive mode -- enter any valid REXX instruction:
           a = 1 + exp(pi() / 2)
           RXMATH interactive mode -- enter any valid REXX instruction:
           say a
           5.8104774
           RXMATH interactive mode -- enter any valid REXX instruction:
           numeric digits 50
           RXMATH interactive mode -- enter any valid REXX instruction:
           b = 1 + exp(pi() / 2); say b
           5.8104773809653516554730356667038331263901708746648
           RXMATH interactive mode -- enter any valid REXX instruction:
           exit 321
           Ready(00321);
 
Usage notes:
 
1. All angles for the trigonometric functions are expressed in radians
   (1 radian = 180/pi degrees).
 
2. In general the numeric arguments to an RXMATH function may be any
   legal REXX numbers, of any size and precision.  If an argument has
   more significant digits than the requested precision of the result
   the argument will be rounded before use.  Of course an attempt to use
   a number outside the range of a given function (e.g., SQRT(-2) or
   FACT(3.3)) will result in an error.  Arguments to the trigonometric
   functions may not be of arbitrary size, but must have an absolute
   value which is < 10 ** (precision % 2).  So for example using
   precision = 9 you could calculate sin(9999), but trying to calculate
   sin(10000) would result in an error.  In the unlikely event that you
   really need to calculate the sine or cosine of a very large number,
   it can be done by increasing the size of the of the precision
   argument until the above condition is met (which may however result
   in an unacceptably long execution time).
 
3. RXMATH is extremely accurate.  If RXMATH returns a number with
   500 significant digits, all 500 should be correct, and the last
   digit should be rounded correctly.  The only exception is when the
   CALC function is given an expression, such as "sin(pi())", which ought
   to equal zero exactly, but instead returns a very small non-zero
   number (such as 6.96470695E-24).  This is unavoidable, since
   any rounding error at all will always be larger than and thus
   dominate a target result of zero.  And of course an expression
   like ((exp(99999) + 1) - exp(99999)) will be evaluated as zero
   rather than one, for an obvious and related reason.
 
4. Some performance considerations.  When the precision is <= 25
   RXMATH can use hard coded values for e and pi (which are used
   internally a lot), and ln(10) (which provides a special boost for
   log base 10 calculations).  The POW function knows when it is being
   asked to take a square root (e.g., RXMATH("POW", 0.25, -1.5) = 8),
   and uses the considerably faster SQRT function when appropriate.
   And RXMATH can recognize situations that REXX can deal with directly,
   such as integer powers, so that for example RXMATH("POW", 2.5, 10)
   will execute much faster than RXMATH("POW", 2.5, 10.1).
 
5. The CALC function takes a character string and interprets it as a
   REXX expression, using the RXMATH math functions with names and
   arguments as listed above (e.g., "exp(4)" is replaced by the
   appropriate call to RXMATH itself).  In addition to the math functions
   supplied by RXMATH the expression may contain any REXX built-in
   functions or any external functions (EXECs or MODULEs) which return
   a numeric result.
 
6. If you call the CALC function without any arguments you will go into
   RXMATH interactive mode.  From there you can issue any valid REXX
   instructions, using any of the math functions supported by RXMATH,
   as well as any other functions or commands usable from within REXX.
   When you enter interactive mode you will be prompted for input.  The
   prompt string is contained in the variable "prompt", and the prompt
   may be altered or suppressed by changing or dropping that variable.
   In addition if there are lines in the system stack the prompt is
   suppressed while those lines are read and executed.  The "return" or
   "exit" instructions will take you out of interactive mode, and may be
   used to return a value if desired.
 
7. The most accurate way to use RXMATH to evaluate complex expressions
   is to use the CALC function non-interactively.  The interactive CALC
   function is slightly less accurate, and gives exactly the same results
   as you would get if instead of using the CALC function you called
   RXMATH to evaluate each math function separately.  Consider the
   following four methods for calculating the expression "pi() - ln(23)",
   using the default precision of 9.
 
   1.  a = rxmath("PI") - rxmath("LN", 23) /* CALC is avoided. */
 
   2.  queue 'a = pi() - ln(23)'
       queue 'return a'
       b = rxmath("CALC") /* Interactive CALC (stacked instructions). */
 
   3.  c = rxmath("CALC", "pi() - ln(23)") /* Non-interactive CALC. */
 
   4.  queue 'c = calc("pi() - ln(23)")' /* CALC from within CALC. */
       queue 'return c'
       d = rxmath("CALC")
 
   Methods 1 and 2 are exactly equivalent, with a = b = 0.00609843.
   Method 3 is more accurate, setting c = 0.00609843766.  In the first
   two cases pi() and ln(23) are both calculated to 9 places, and their
   difference is taken, giving a result with fewer than 9 digits of
   precision.  In case 3 RXMATH calculates both pi() and ln(23) to an
   internal precision that is greater than the precision requested by
   the user, takes the difference (again with greater precision),
   and then rounds the final result to a full 9 digits of accuracy before
   returning it.  Method 4 invokes the non-interactive CALC function
   from within interactive CALC.  This may seem a bit confusing, but
   it is perfectly legitimate (CALC being an RXMATH function like any
   other), and is exactly equivalent to method 3.  Just make sure that
   the argument is a quoted string, so it isn't evaluated before it
   gets into the non-interactive CALC function.  There are also a number
   of other ways to invoke CALC from within CALC, which are amusing but
   not terribly useful.
 
.cs 5 off
-------- Cut here -------- End of RXMATH HELPREXX -----------------------
-------- End of RXMATH math package for REXX ----------------------------
-- 
John Brock
uunet!csfb1!jbrock
jbrock@csfb1.fir.fbc.com


