Articles   Members Online:
-Article/Tip Search
-News Group Search over 21 Million news group articles.
Member Area
-Account Center
-Top 10 NEW!!
-Submit Article/Tip
-Forums Upgraded!!
-My Articles
-Edit Information
-Become a Member
-Why sign up!
-Chat Online!
-Indexes NEW!!
-Build your resume
-Find a job
-Post a job
-Resume Search
-Link to us
Visit Embarcadero
Embarcadero Community
Using the Math Unit Turn on/off line numbers in source code. Switch to Orginial background IDE or DSP color Comment or reply to this aritlce/tip for discussion. Bookmark this article to my favorite article(s). Print this article
Delphi 2.x
User Rating
No Votes
# Votes
DSP, Administrator
Reference URL:
			Author: Lou Adler

I've heard of the Math unit that's included with the Developer level version of 
Delphi. How do I use it?


An Unsung Hero?

If you haven't heard of the Math unit, you're not alone. It's one of those units 
that's kind of buried in the myriad directories under the Delphi directory. Also, 
it's only included in the Delphi 2.0 Developer version and above. For those of you 
who have Delphi 2.0 Developer, you can find the source code file in the 
\\Borland\Delphi 2.0\Source\RTL\SYS directory.

This unit is one of those things I've heard a lot of people mention in the past but 
unfortunately, I haven't seen many examples of using it. I'm not sure whether it's 
because developers don't use it much or don't know about it. In any case, it's a 
shame because what the Math unit has to offer could be helpful to people needing to 
use mathematical functions in their code.

Not only are they helpful by their mere existence, but several of the member 
functions are written in Assembly language that is optimized for the Pentium FPU, 
so they're incredibly fast and efficient. For example, the procedure SinCos, which 
is one of the procedures written in Assembly, will produce the Sin and Cos 
simultaneously of an any angle faster than if you called the Sin and Cos functions 
individually. I've seen it at work and it's amazing.

The Math unit includes several categories of mathematical functions you can 
incorporate into your code. These include:

Trigonometric, Hyperbolic and Angle Conversion Functions
Logorithmic Functions
Exponential Functions
Some Miscellaneous Computing Functions
Several Statistical Functions
The Standard Set of Quattro Pro Financial Functions

Mind you, not all of the functions are coded in Assembly, but the mere fact that 
they've already been written means you don't have to, so that's a real time saver. 
Below are the function prototypes for the unit, so you can see what's in the file:
2   {-----------------------------------------------------------------------
3   Copyright (c) Borland International.
5   Most of the following trig and log routines map directly to Intel 80387 FPU
6   floating point machine instructions.  Input domains, output ranges, and
7   error handling are determined largely by the FPU hardware.
8   Routines coded in assembler favor the Pentium FPU pipeline architecture.
9   -----------------------------------------------------------------------}
11  { Trigonometric functions }
12  function ArcCos(X: Extended): Extended;  { IN: |X| <= 1  OUT: [0..PI] radians }
13  function ArcSin(X: Extended): Extended;  { IN: |X| <= 1  OUT: [-PI/2..PI/2] radians 
14  }
16  { ArcTan2 calculates ArcTan(Y/X), and returns an angle in the correct quadrant.
17    IN: |Y| < 2^64, |X| < 2^64, X <> 0   OUT: [-PI..PI] radians }
18  function ArcTan2(Y, X: Extended): Extended;
20  { SinCos is 2x faster than calling Sin and Cos separately for the same angle }
21  procedure SinCos(Theta: Extended; var Sin, Cos: Extended) register;
22  function Tan(X: Extended): Extended;
23  function Cotan(X: Extended): Extended;           { 1 / tan(X), X <> 0 }
24  function Hypot(X, Y: Extended): Extended;        { Sqrt(X**2 + Y**2) }
26  { Angle unit conversion routines }
27  function DegToRad(Degrees: Extended): Extended;  { Radians := Degrees * PI / 180}
28  function RadToDeg(Radians: Extended): Extended;  { Degrees := Radians * 180 / PI }
29  function GradToRad(Grads: Extended): Extended;   { Radians := Grads * PI / 200 }
30  function RadToGrad(Radians: Extended): Extended; { Grads := Radians * 200 / PI }
31  function CycleToRad(Cycles: Extended): Extended; { Radians := Cycles * 2PI }
32  function RadToCycle(Radians: Extended): Extended;{ Cycles := Radians / 2PI }
34  { Hyperbolic functions and inverses }
35  function Cosh(X: Extended): Extended;
36  function Sinh(X: Extended): Extended;
37  function Tanh(X: Extended): Extended;
38  function ArcCosh(X: Extended): Extended;   { IN: X >= 1 }
39  function ArcSinh(X: Extended): Extended;
40  function ArcTanh(X: Extended): Extended;   { IN: |X| <= 1 }
42  { Logorithmic functions }
43  function LnXP1(X: Extended): Extended;   { Ln(X + 1), accurate for X near zero }
44  function Log10(X: Extended): Extended;                     { Log base 10 of X}
45  function Log2(X: Extended): Extended;                      { Log base 2 of X }
46  function LogN(Base, X: Extended): Extended;                { Log base N of X }
48  { Exponential functions }
50  { IntPower: Raise base to an integral power.  Fast. }
51  function IntPower(Base: Extended; Exponent: Integer): Extended register;
53  { Power: Raise base to any power.
54    For fractional exponents, or exponents > MaxInt, base must be > 0. }
55  function Power(Base, Exponent: Extended): Extended;
58  { Miscellaneous Routines }
60  { Frexp:  Separates the mantissa and exponent of X. }
61  procedure Frexp(X: Extended; var Mantissa: Extended; var Exponent: Integer) 
62  register;
64  { Ldexp: returns X*2**P }
65  function Ldexp(X: Extended; P: Integer): Extended register;
67  { Ceil: Smallest integer >= X, |X| < MaxInt }
68  function Ceil(X: Extended):Integer;
70  { Floor: Largest integer <= X,  |X| < MaxInt }
71  function Floor(X: Extended): Integer;
73  { Poly: Evaluates a uniform polynomial of one variable at value X.
74      The coefficients are ordered in increasing powers of X:
75      Coefficients[0] + Coefficients[1]*X + ... + Coefficients[N]*(X**N) }
76  function Poly(X: Extended; const Coefficients: array of Double): Extended;
78  {-----------------------------------------------------------------------
79  Statistical functions.
81  Common commercial spreadsheet macro names for these statistical and
82  financial functions are given in the comments preceding each function.
83  -----------------------------------------------------------------------}
85  { Mean:  Arithmetic average of values.  (AVG):  SUM / N }
86  function Mean(const Data: array of Double): Extended;
88  { Sum: Sum of values.  (SUM) }
89  function Sum(const Data: array of Double): Extended register;
90  function SumOfSquares(const Data: array of Double): Extended;
91  procedure SumsAndSquares(const Data: array of Double;
92    var Sum, SumOfSquares: Extended) register;
94  { MinValue: Returns the smallest signed value in the data array (MIN) }
95  function MinValue(const Data: array of Double): Double;
97  { MaxValue: Returns the largest signed value in the data array (MAX) }
98  function MaxValue(const Data: array of Double): Double;
100 { Standard Deviation (STD): Sqrt(Variance). aka Sample Standard Deviation }
101 function StdDev(const Data: array of Double): Extended;
103 { MeanAndStdDev calculates Mean and StdDev in one pass, which is 2x faster than
104   calculating them separately.  Less accurate when the mean is very large
105   (> 10e7) or the variance is very small. }
106 procedure MeanAndStdDev(const Data: array of Double; var Mean, StdDev: Extended);
108 { Population Standard Deviation (STDP): Sqrt(PopnVariance).
109   Used in some business and financial calculations. }
110 function PopnStdDev(const Data: array of Double): Extended;
112 { Variance (VARS): TotalVariance / (N-1). aka Sample Variance }
113 function Variance(const Data: array of Double): Extended;
115 { Population Variance (VAR or VARP): TotalVariance/ N }
116 function PopnVariance(const Data: array of Double): Extended;
118 { Total Variance: SUM(i=1,N)[(X(i) - Mean)**2] }
119 function TotalVariance(const Data: array of Double): Extended;
121 { Norm:  The Euclidean L2-norm.  Sqrt(SumOfSquares) }
122 function Norm(const Data: array of Double): Extended;
124 { MomentSkewKurtosis: Calculates the core factors of statistical analysis:
125   the first four moments plus the coefficients of skewness and kurtosis.
126   M1 is the Mean.  M2 is the Variance.
127   Skew reflects symmetry of distribution: M3 / (M2**(3/2))
128   Kurtosis reflects flatness of distribution: M4 / Sqr(M2) }
130 procedure MomentSkewKurtosis(const Data: array of Double;
131   var M1, M2, M3, M4, Skew, Kurtosis: Extended);
133 { RandG produces random numbers with Gaussian distribution about the mean.
134   Useful for simulating data with sampling errors. }
135 function RandG(Mean, StdDev: Extended): Extended;
137 {-----------------------------------------------------------------------
138 Financial functions.  Standard set from Quattro Pro.
140 Parameter conventions:
142 From the point of view of A, amounts received by A are positive and
143 amounts disbursed by A are negative (e.g. a borrower's loan repayments
144 are regarded by the borrower as negative).
146 Interest rates are per payment period.  11% annual percentage rate on a
147 loan with 12 payments per year would be (11 / 100) / 12 = 0.00916667
149 -----------------------------------------------------------------------}
151 type
152   TPaymentTime = (ptEndOfPeriod, ptStartOfPeriod);
154 { Double Declining Balance (DDB) }
155 function DoubleDecliningBalance(Cost, Salvage: Extended;
156   Life, Period: Integer): Extended;
158 { Future Value (FVAL) }
159 function FutureValue(Rate: Extended; NPeriods: Integer; Payment, PresentValue:
160   Extended; PaymentTime: TPaymentTime): Extended;
162 { Interest Payment (IPAYMT)  }
163 function InterestPayment(Rate: Extended; Period, NPeriods: Integer; PresentValue,
164   FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
166 { Interest Rate (IRATE) }
167 function InterestRate(NPeriods: Integer;
168   Payment, PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): 
169 Extended;
171 { Internal Rate of Return. (IRR) Needs array of cash flows. }
172 function InternalRateOfReturn(Guess: Extended;
173   const CashFlows: array of Double): Extended;
175 { Number of Periods (NPER) }
176 function NumberOfPeriods(Rate, Payment, PresentValue, FutureValue: Extended;
177   PaymentTime: TPaymentTime): Extended;
179 { Net Present Value. (NPV) Needs array of cash flows. }
180 function NetPresentValue(Rate: Extended; const CashFlows: array of Double;
181   PaymentTime: TPaymentTime): Extended;
183 { Payment (PAYMT) }
184 function Payment(Rate: Extended; NPeriods: Integer;
185   PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
187 { Period Payment (PPAYMT) }
188 function PeriodPayment(Rate: Extended; Period, NPeriods: Integer;
189   PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
191 { Present Value (PVAL) }
192 function PresentValue(Rate: Extended; NPeriods: Integer;
193   Payment, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
195 { Straight Line depreciation (SLN) }
196 function SLNDepreciation(Cost, Salvage: Extended; Life: Integer): Extended;
198 { Sum-of-Years-Digits depreciation (SYD) }
199 function SYDDepreciation(Cost, Salvage: Extended; Life, Period: Integer): 
200 Extended;

Clearing Things Up: Usage

Whew! That's a lot of information to digest! I listed it here to impress upon you 
just how much there is. For those of you creating financial applications, the 
financial functions will come in handy (I sure wish I had these functions available 
when I was writing financial software).

Listing the functions is one thing - actually using them is another. As you can 
see, most of the input parameters require an Extended numeric type, which is a 
10-byte number ranging from 3.4 * 10e-4932 to 1.1 * 10e4932 in scientific notation. 
In other words, you can have incredibly huge numbers as input values.

Take a moment to look at the statistical functionsBOOK1. Notice anything odd about 
almost all of the functions' input parameters? Most of them take a constant open 
array of double! This implies you can pass any size array as an input parameter, 
but it must be passed as a constant array, which means that you have to pass the 
array in the form of (1, 2, 3, 4, 5, 6 ..). That's not so difficult with small 
known sets of numbers; just hard code them in. But arrays in Pascal are typically 
of the variable type, where you define a finite number of elements, then fill in 
the element values. This poses a bit of a problem. Fortunately, there's a solution.

Buried in the System unit is a function called Slice: function Slice(var A: array; 
Count: Integer): array; Essentially, what Slice does is take a certain number of 
elements from an array, starting at the beginning, and passes the slice of the 
array as an open array parameter, fooling the compiler into thinking a constant 
array is being passed. This means that you can pass the entire array or smaller 
subset. In fact, Slice can only be used within the context of being passed as an 
open array parameter. Using it outside of this context will create a compiler 
error. What a convenient function! So, we can define a variable type array as we're 
used to in Delphi, fill it up, put it into Slice, which is then used in one of the 
functions. For example: MyExtendedNumber := Mean(Slice(MyArray, NumElementsPassed));

At this point, you're probably thinking this is pretty incredible stuff. But 
there's one thing that still bothers me about it: The place where the statistical 
functions would be most useful is on columnar calculations on tables. 
Unfortunately, you never know how many records are in a table until runtime. 
Granted, depending upon the amount of RAM in your system, you could make an 
incredibly large array of let's say 100K elements, fill it up to the record count 
of your table, then apply Slice to grab only those you need. However, that's pretty 
inefficient. Also, in my immediate experience, many of my tables have well over 
100K records, which means I'd have to hard code an even greater upper limit. But 
there will also be tables that have far fewer records than 100K - more like 10K. So 
the idea then is to strike a balance. No thanks!

Doing the DynArray Thing

So where am I if I can't accept defining a huge array, or making some sort of size 
compromise? I guess I need to create a variable sized array whose size can be 
defined at runtime.

Wait a minute! You're not supposed to be able to do that in Delphi!

You can, but it takes some pointers to be able to pull it off. For an in-depth 
discussion of the technique, I'm going to point you to an article on the enquiry 
site entitled Runtime Resizeable Arrays, which will show you how to create an array 
that has an element count you don't know about until runtime. I highly suggest 
reading the article before continuing, if you're not familiar with the technique.

What gives you the ability to create a dynamic array is a function like the 
following (this in the article):

201 type
202   TResizeArr = array[0..0] of string;
203   PResizeArr = ^TResizeArr;
204   ...
206   {============================================================================
207    Procedure which defines the dynarray. Note that the THandle and Pointer to
208    the array are passed by reference. This is so that they may defined outside
209    the scope of this proc.
210    ============================================================================}
212 procedure DefineDynArray(var h: THandle; {Handle to mem pointer}
213   NumElements: LongInt; {Number of items in array}
214   var PArr: PResizeArr); {Pointer to array struct}
215 begin
216   {Allocate Windows Global Heap memory}
217   h := GlobalAlloc(GMEM_FIXED, NumElements * sizeof(TResizeArr));
218   PArr := GlobalLock(h);
219 end;

Note that you can just as easily replace the array of String with an array of 
Double. In any case, the gist of what the procedure does is allocate memory for the 
number of elements that you want to have in your array (TResizeArr), then locks 
that area of the heap and assigns it to an array pointer (PResizeArr). To load 
values into the array, you de-reference the pointer as follows: MyPointerArray^[0] 
:= 1234.1234;

To pass the entire array into the Mean function as above, all we have to do is 
de-reference the entire array as follows: MyExtendedNumber := 
Mean(Slice(MyPointerArray^, NumElementsPassed));

Putting It All Together

I mentioned above that the best place to employ the statistical functions is in 
performing statistics on columnar data in a table. The unit code below provides a 
simple example of loading a DynArray with columnar data, then performing the Mean 
function on the loaded array. Note the table that I used had about 80K records in 

220 unit parrmain;
222 interface
224 uses
225   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
226   DB, DBTables, ComCtrls, Math, StdCtrls, ds_stats;
228 type
229   TResizeArr = array[0..0] of Double;
230   PResizeArr = ^TResizeArr;
231   TForm1 = class(TForm)
232     StatusBar1: TStatusBar;
233     Button1: TButton;
234     Table1: TTable;
235     procedure Button1Click(Sender: TObject);
236   private
237     { Private declarations }
238     PA: PResizeArr;
239     Hndl: THandle;
240     procedure DefineDynArray(var H: THandle;
241       NumElem: LongInt;
242       var PArr: PResizeArr);
243     procedure FillArray;
244     procedure SaveScreen;
245   public
246     { Public declarations }
248   end;
250 var
251   Form1: TForm1;
253 implementation
255 {$R *.DFM}
257 procedure TForm1.DefineDynArray(var H: THandle;
258   NumElem: LongInt;
259   var PArr: PResizeArr);
260 begin
261   H := GlobalAlloc(GMEM_FIXED, NumElem * SizeOf(TResizeArr));
262   PArr := GlobalLock(H);
263 end;
265 procedure TForm1.LoadArray;
266 var
267   tbl: TTable;
268   recs: LongInt;
269 begin
270   tbl := TTable.Create(Application); //Create a TTable instance
271   with tbl do
272   begin //and set properties
273     Active := False;
274     DatabaseName := 'Primary';
275     TableName := 'Test';
276     TableType := ttParadox;
277     Open;
279     recs := RecordCount; //Get number of records in table
281     DefineDynArray(Hndl, recs, PA); //Now, define our dynarray
282     recs := 0; //Reset recs for reuse
284     StatusBar1.SimpleText := 'Now filling array';
286     while not EOF do
287     begin
288       Application.ProcessMessages; //allow background processing
289       try
290         PA^[recs] := FieldByName('Charge').AsFloat;
291         StatusBar1.SimpleText := 'Grabbed value of: ' + FloatToStr(PA^[recs]);
292         StatusBar1.Invalidate;
293         Inc(recs);
294         Next;
295       except
296         GlobalUnlock(Hndl);
297         GlobalFree(Hndl);
298         Exit;
299       end;
300     end;
302     //Pop up a message to show what was calculated.
303     ShowMessage(FloatToStr(Mean(Slice(PA^, RecordCount))));
304       //pass the array using Slice
305     GlobalUnlock(Hndl); //Unlock and Free memory and TTable instance.
306     GlobalFree(Hndl);
307     tbl.Free;
308   end;
309 end;
311 procedure TForm1.Button1Click(Sender: TObject);
312 begin
313   LoadArray;
314 end;
316 end.

You could get pretty complex with this by creating a component that encapsulates 
the statistical functions and grabs data off a table. Using the principles of the 
code above, it shouldn't be too hard to do. Follow the code; better yet, try it 
out, supply your own values, and see what you come up with.

We covered a lot of ground here. I wasn't happy to tell you merely about the Math 
unit and all the wonderful routines it contains; I wanted to show you a way to 
employ a major portion of it in as flexible a way as possible.

In my opinion, it's not enough just to know about something in programming; you have to know how to use it. With the material I've presented, you should be able to employ the functions of the Math unit in very little time.

Vote: How useful do you find this Article/Tip?
Bad Excellent
1 2 3 4 5 6 7 8 9 10


Share this page
Download from Google

Copyright © Mendozi Enterprises LLC