Mathematics.hel
Introduction
REXX does not have an 'include' facility nor 'arbitrary precision' mathematical functions out of the
box. In many older REXX entries it was custom the have all needed routines and procedures copied into
the program. This is cumbersome and redundant. Newer entries redirect to
Libraries and Pre processor and Include clause how to use them.
At the end of each entry you'll find several 'include' clauses, showing the libraries that the program needs (just like '#include', 'import', 'uses' or '::requires').
Both Regina and ooRexx are supplied with a mathematical library, a DLL based on the C-library. This library supports some basic math functions and a few constants, up to 16 digits precision. For many tasks on RosettaCode this is not sufficient.
In 2024 I (Zeddicus) started posting on RosettaCode and found the copy-and-paste WoW to get a runnable program annoying. Therefore I'll gather all procedures I have until now and present them here in several modules and stand alone files. Other REXX entries will link to this page. It's not quite correct to name these modules 'libraries'. They are just REXX source code that becomes part of your program.
Many sources were helpful while creating these libraries. Credits go to:
- Math functions in REXX: John Brock, Patrick McPhee, several other sources
- Code snippets RosettaCode: Gerard Schildberger, Walter Pachl
- Wikipedia International
- Wolfram Mathworld
- Geeks for Geeks
- Online Encyclopedia of Integer Sequences
- ChatGPT4
- Numerical methods at work: Henrik Vestermark
- Numerical recipes: Press et al
- Various other books and internet math sources and online calculators
- And my own research and try-outs
General remarks
Most procedures work only with real arguments and deliver real results. However, there are special libraries capable to perform actions on other types of data: Complex (complex arithmetic and functions), Polynomial (polynomial arithmetic and functions) and Rational (working with fractions).
Out of the box, REXX can work with arbitrary precision, thus also these procedures, using your current numeric digits setting. However, basic functions such as Sqrt, Pi or Tan start becoming slower at 1000 digits and more, and the more complex functions as Gamma or Zeta struggle already with 100 digits. For most results you may expect the least significant digit (from your numeric digits setting) to be off at most 1. However, this cannot be guaranteed, so be careful to rely on all digits.
Each procedure performs its own parameter checking. In case of wrong parameters, your program will fail at the proper place. This ensures that a procedure may be used stand-alone, not relying on the caller for its parameters. Though this introduces a lot of overhead, it makes the modules more robust.
All routines are coded with the 'procedure' clause, meaning all variables within are local. They communicate via the parameters. However, sometimes it's handy or needed to use global variables (store multiple results, memorization). For this the stem 'glob.' is used, and many procedures have 'expose glob.' coded to make it available. Some procedures also use extra stems, like prim. or cueq. Exposed stems always follow the 'xxxx.' naming convention.
As you might expect, there are many MANY cross references between the modules.
Usage
Use the low profile pre processor (include pieces of code in a REXX program): Low profile REXX pre processor.
Using this tool is highly recommended. Besides including external code, it also handles conditions in a neat way. I recommend copying and running the self-extractor in a folder of your choice: it extracts all modules and programs including demos, the pre processor and documentation.
The documentation includes a list of all modules, a list of all procedures/routines, an extract of all comments in the source and other useful reports.
or (not recommended, after extraction):
Copy and paste (parts) of the libraries/procedures in your program.
Add a line
glob. = ''
in your main routine.
Many REXX entries use this technique, leading to a lot of duplicated code. After resolving all cross refs, your program will run without the preprocessor.
Complex
This library supports the basic arithmetic operations, modulus, argument, conjugate, rectangular<->polar and more. Complex numbers a+bi are coded as string (list): 'a b'. You may leave out b, but not a. Thus 2 = '2', 2i = '0 2' and 2+2i = '2 2'. Procedures Cadd, Cdiv, Cmul and Csub accept any number of arguments, thus facilitating repeated addition etc. All the usual trigo functions are present, with their inverses and hyperbolic forms. Exp, Ln and Pow(er) are also supported. Several tools such as polynomial evaluation and formatting are included.
Constants
A whole bunch of mathematical constants: Pi, E, Euler-Mascheroni and a lot more. Up to 100 digits, these constants are predefined and thus given directly. Above 100 digits, under availability of a acceptable fast algorithm, the constant is calculated. All procedures in this library have no parameters.
Functions
Here we have the well known set of trigonometric, hyperbolic, exponential and logarithmic functions and their inverses. And of course functions like Square/Cubic/Quartic/Nth root. Some 'higher' functions are also included: Beta, Gamma, Zeta and a few more. Arguments for the trig functions is always in radians, maybe you like to use Deg or Rad to convert values. Several functions more related to number theory are present. This library accepts only real arguments.
Numbers
These are procedures belonging to the field 'number theory': all kinds of numbers, combinations, factors, divisors, and more. Most procedures expect a number as parameter, some two numbers or even three. Many of these procedures become slower as the numbers grow.
Polynomial
This set of procedures deal with 'polynomial processing', such as add, exponentiate, convert to a formula and more. The external representation of a polynomial is as a list, implemented in a string. The adopted convention is alike most text books and the internet: coefficients are stored highest power first and missing terms included as 0. Thus x^4+2x^2+x = '1 0 2 1 0' and x-1 = '1 -1'.
Rational
One of the tasks on RosettaCode is 'Rational arithmetic'. The most important procedures from this field are included here. Besides the second parameter of Rpower, all arguments in and out have the form 'numerator denominator'. The denominator may be left out, taking value 1. Thus 1/2 = '1 2' and 2/1 = 2 = '2'.
Roots
For equations of the first to fourth degree solutions in closed form (formulas) are possible. Procedures for these equations are present here. The input follows the convention as described in Polynomial: so solving x^2-3x+2=0 is represented by parameter '1 -3 2'. Complex number support comes with its own procedures.
Sequences
These procedures also belong to the category 'number theory'. They return the count of found numbers, and the numbers itself in stems to be processed by the calling program. There might be a counterpart in Numbers: for example Prime(x) tests the primality of x and Primes(x) collects all primes up to x.
Math
This module contains the code of all above modules, so 'include Math' will make everything available for your program. Using Math, you don't have to bother about cross references and missing includes. It's about 8k lines, so may have some effect on the performance.
Abnormal end handler
This is a standard handler of the REXX conditions causing a program to stop, such as SYNTAX, NOVALUE, NOTREADY and HALT. There are 2 parts: a code snippet Settings to set the conditions and a library Abend to handle them. Include Settings always at the beginning of your program or main procedure if present. And include Abend at the end. The handler also traps wrong arguments.
Consider below program. Name it Abend.rex if want to try it.
Main:
include Settings
say version; say 'Abnormal program end handler'; say
call TriggerSyntax
call TriggerNovalue
call TriggerNotready
call TriggerHalt
call TriggerCalls
call TriggerArgs
exit
TriggerSyntax:
procedure
say 'Trigger syntax...'
f = 1; g = 2; h = 3
say f+g h/0 g-h
return
TriggerNovalue:
procedure
say 'Trigger no value...'
f = 1; g = 2; h = 3; i = absent; j = 1/h
return
TriggerNotready:
procedure
say 'Trigger not ready...'
file = 'absent'
call Stream file,'c','open read'
return
TriggerHalt:
procedure
say 'Trigger halt...'
do forever
f = 1; g = 2; h = 3
end
return
TriggerCalls:
procedure
say 'Trigger calls...'
call Sub1 123.456
return
TriggerArgs:
procedure
say 'Trigger arguments...'
call Sub3 -1
return
Sub1:
procedure
arg xx
xx = xx/2
call Sub2 xx
return
Sub2:
procedure
arg xx
xx = xx/4
g = xx/absent
return
Sub3:
procedure
arg xx
call Sub4 xx
return
Sub4:
procedure
arg xx
if xx < 0 then say abend
return
include Abend
Using 'rexx|regina run Abend' will generate Runner.rex, check this to see how it works. After being generated, you may run Runner.rex the useal way, i.e. 'rexx|regina runner'. If I comment out all the trap condition settings in Runner.rex and run the 6 calls in Main: one by one, then I get the standard error handling:
REXX-Regina_3.9.6(MT) 5.00 29 Apr 2024 Abnormal program end handler Trigger syntax... 31 +++ say f+g h/0 g-h 20 +++ call TriggerSyntax Error 42 running "C:\Rex\Rosetta\runner.rex", line 31: Arithmetic overflow/underflow Error 42.3: Arithmetic overflow; divisor must not be zero Trigger no value... Trigger not ready... Trigger halt... 51 +++ g = 2 23 +++ call TriggerHalt Error 4 running "C:\Rex\Rosetta\runner.rex", line 51: Program interrupted Trigger calls... 79 +++ g = xx/absent 72 +++ call Sub2 xx 59 +++ call Sub1 123.456 24 +++ call TriggerCalls Error 41 running "C:\Rex\Rosetta\runner.rex", line 79: Bad arithmetic conversion Error 41.2: Non-numeric value ("ABSENT") to right of arithmetic operation "/" Trigger arguments... ARGUMENT
ooRexx output is similar. The 'NOVALUE' condition is standard not triggered. Very likely your program will fail somewhere else. The 'NOTREADY' condition is standard also not triggered. Your program might fail elsewhere or produce no output. An advantage of this standard approach is the showing of the 'call stack', i.e. Runner -> Abend -> call Trigger... -> clause in error. This helps very much while debugging.
Using customized 'signal on' trapping, this call stack information is not available. Therefore I added a trick in routine Abend to force the display of the call stack.
Argument errors are trapped by clauses like 'if xx < 0 then say argument'. This raises the NOVALUE condition, which is recognized by routine Abend, as seen below.
Now, the same action with all condition traps enabled, produces following output for Regina:
REXX-Regina_3.9.6(MT) 5.00 29 Apr 2024 Abnormal program end handler Trigger syntax... Rexx : REXX-Regina_3.9.6(MT) 5.00 29 Apr 2024 Source : C:\Rex\Rosetta\Runner.rex Line : 32 say f+g h/0 g-h Var : f = "1" Var : g = "2" Var : h = "3" Digits : 9 Signal : Runtime error Error : 42 Arithmetic overflow/underflow Reason : Error 42.3: Arithmetic overflow; divisor must not be zero 166 +++ say IgnoreError16 20 +++ call TriggerSyntax Error 16 running "C:\Rex\Rosetta\runner.rex", line 166: Label not found Error 16.1: Label "IGNOREERROR16" not found Trigger no value... Rexx : REXX-Regina_3.9.6(MT) 5.00 29 Apr 2024 Source : C:\Rex\Rosetta\Runner.rex Line : 38 f = 1; g = 2; h = 3; i = absent; j = 1/h Var : f = "1" Var : g = "2" Var : h = "3" Digits : 9 Signal : Variable has no value Reason : ABSENT 166 +++ say IgnoreError16 21 +++ call TriggerNovalue Error 16 running "C:\Rex\Rosetta\runner.rex", line 166: Label not found Error 16.1: Label "IGNOREERROR16" not found Trigger not ready... Rexx : REXX-Regina_3.9.6(MT) 5.00 29 Apr 2024 Source : C:\Rex\Rosetta\Runner.rex Line : 45 call Stream file,'c','open read' Var : file = "absent" Digits : 9 Signal : File does not exist or is in use Reason : absent 166 +++ say IgnoreError16 22 +++ call TriggerNotready Error 16 running "C:\Rex\Rosetta\runner.rex", line 166: Label not found Error 16.1: Label "IGNOREERROR16" not found Trigger halt... Rexx : REXX-Regina_3.9.6(MT) 5.00 29 Apr 2024 Source : C:\Rex\Rosetta\Runner.rex Line : 52 f = 1; g = 2; h = 3 Var : f = "1" Var : g = "2" Var : h = "3" Digits : 9 Signal : Program interrupted 166 +++ say IgnoreError16 23 +++ call TriggerHalt Error 16 running "C:\Rex\Rosetta\runner.rex", line 166: Label not found Error 16.1: Label "IGNOREERROR16" not found Trigger calls... Rexx : REXX-Regina_3.9.6(MT) 5.00 29 Apr 2024 Source : C:\Rex\Rosetta\Runner.rex Line : 79 g = xx/absent Var : xx = "15.432" Digits : 9 Signal : Variable has no value Reason : ABSENT 166 +++ say IgnoreError16 72 +++ call Sub2 xx 59 +++ call Sub1 123.456 24 +++ call TriggerCalls Error 16 running "C:\Rex\Rosetta\runner.rex", line 166: Label not found Error 16.1: Label "IGNOREERROR16" not found Trigger arguments... Rexx : REXX-Regina_3.9.6(MT) 5.00 29 Apr 2024 Source : C:\Rex\Rosetta\Runner.rex Line : 91 if xx < 0 then say argument Arg : xx = "-1" Digits : 9 Signal : Argument outside domain or invalid 166 +++ say IgnoreError16 85 +++ call Sub4 xx 65 +++ call Sub3 -1 25 +++ call TriggerArgs Error 16 running "C:\Rex\Rosetta\runner.rex", line 166: Label not found Error 16.1: Label "IGNOREERROR16" not found
And for ooRexx:
REXX-ooRexx_5.0.0(MT)_64-bit 6.05 23 Dec 2022 Abnormal program end handler Trigger syntax... Rexx : REXX-ooRexx_5.0.0(MT)_64-bit 6.05 23 Dec 2022 Source : C:\Rex\Rosetta\Runner.rex Line : 32 say f+g h/0 g-h Var : f = "1" Var : g = "2" Var : h = "3" Digits : 9 Signal : Runtime error Error : 42 Arithmetic overflow/underflow. Line : 20 call TriggerSyntax 168 *-* say IgnoreError16 Error 16 running C:\Rex\Rosetta\Runner.rex line 168: Label not found. Error 16.1: Label "IGNOREERROR16" not found. Trigger no value... Rexx : REXX-ooRexx_5.0.0(MT)_64-bit 6.05 23 Dec 2022 Source : C:\Rex\Rosetta\Runner.rex Line : 38 f = 1; g = 2; h = 3; i = absent; j = 1/h Var : f = "1" Var : g = "2" Var : h = "3" Digits : 9 Signal : Variable has no value Reason : ABSENT Line : 21 call TriggerNovalue 168 *-* say IgnoreError16 Error 16 running C:\Rex\Rosetta\Runner.rex line 168: Label not found. Error 16.1: Label "IGNOREERROR16" not found. Trigger not ready... Rexx : REXX-ooRexx_5.0.0(MT)_64-bit 6.05 23 Dec 2022 Source : C:\Rex\Rosetta\Runner.rex Line : 45 call Stream file,'c','open read' Var : file = "absent" Digits : 9 Signal : File does not exist or is in use Reason : absent Line : 22 call TriggerNotready 168 *-* say IgnoreError16 Error 16 running C:\Rex\Rosetta\Runner.rex line 168: Label not found. Error 16.1: Label "IGNOREERROR16" not found. Trigger halt... Rexx : REXX-ooRexx_5.0.0(MT)_64-bit 6.05 23 Dec 2022 Source : C:\Rex\Rosetta\Runner.rex Line : 52 f = 1; g = 2; h = 3 Var : f = "1" Var : g = "2" Var : h = "3" Digits : 9 Signal : Program interrupted Line : 23 call TriggerHalt 168 *-* say IgnoreError16 Error 16 running C:\Rex\Rosetta\Runner.rex line 168: Label not found. Error 16.1: Label "IGNOREERROR16" not found. Trigger calls... Rexx : REXX-ooRexx_5.0.0(MT)_64-bit 6.05 23 Dec 2022 Source : C:\Rex\Rosetta\Runner.rex Line : 79 g = xx/absent Var : xx = "15.432" Digits : 9 Signal : Variable has no value Reason : ABSENT Line : 72 call Sub2 xx Var : xx = "61.728" Line : 59 call Sub1 123.456 Line : 24 call TriggerCalls 168 *-* say IgnoreError16 Error 16 running C:\Rex\Rosetta\Runner.rex line 168: Label not found. Error 16.1: Label "IGNOREERROR16" not found. Trigger arguments... Rexx : REXX-ooRexx_5.0.0(MT)_64-bit 6.05 23 Dec 2022 Source : C:\Rex\Rosetta\Runner.rex Line : 91 if xx < 0 then say argument Arg : xx = "-1" Digits : 9 Signal : Argument outside domain or invalid Line : 85 call Sub4 xx Var : xx = "-1" Line : 65 call Sub3 -1 Line : 25 call TriggerArgs 168 *-* say IgnoreError16 Error 16 running C:\Rex\Rosetta\Runner.rex line 168: Label not found. Error 16.1: Label "IGNOREERROR16" not found.
Nice layout... and it shows the values of all recognized variables for the line(s) in error. As you see, the ooRexx output differs from the Regina output, but both show the complete call tree from bottom to top. ooRexx happens also to show the arguments in the call tree! The errors 16 may be ignored, that's just the trick to get the call tree information.
It's up to you which method you prefer. You may easily switch between them by (not) using 'include Settings' and 'include Abend'.