Using the Math Unit  
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:
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: { Copyright (c) Borland International. Most of the following trig and log routines map directly to Intel 80387 FPU floating point machine instructions. Input domains, output ranges, and error handling are determined largely by the FPU hardware. Routines coded in assembler favor the Pentium FPU pipeline architecture. } { Trigonometric functions } function ArcCos(X: Extended): Extended; { IN: X <= 1 OUT: [0..PI] radians } function ArcSin(X: Extended): Extended; { IN: X <= 1 OUT: [PI/2..PI/2] radians } { ArcTan2 calculates ArcTan(Y/X), and returns an angle in the correct quadrant. IN: Y < 2^64, X < 2^64, X <> 0 OUT: [PI..PI] radians } function ArcTan2(Y, X: Extended): Extended; { SinCos is 2x faster than calling Sin and Cos separately for the same angle } procedure SinCos(Theta: Extended; var Sin, Cos: Extended) register; function Tan(X: Extended): Extended; function Cotan(X: Extended): Extended; { 1 / tan(X), X <> 0 } function Hypot(X, Y: Extended): Extended; { Sqrt(X**2 + Y**2) } { Angle unit conversion routines } function DegToRad(Degrees: Extended): Extended; { Radians := Degrees * PI / 180} function RadToDeg(Radians: Extended): Extended; { Degrees := Radians * 180 / PI } function GradToRad(Grads: Extended): Extended; { Radians := Grads * PI / 200 } function RadToGrad(Radians: Extended): Extended; { Grads := Radians * 200 / PI } function CycleToRad(Cycles: Extended): Extended; { Radians := Cycles * 2PI } function RadToCycle(Radians: Extended): Extended;{ Cycles := Radians / 2PI } { Hyperbolic functions and inverses } function Cosh(X: Extended): Extended; function Sinh(X: Extended): Extended; function Tanh(X: Extended): Extended; function ArcCosh(X: Extended): Extended; { IN: X >= 1 } function ArcSinh(X: Extended): Extended; function ArcTanh(X: Extended): Extended; { IN: X <= 1 } { Logorithmic functions } function LnXP1(X: Extended): Extended; { Ln(X + 1), accurate for X near zero } function Log10(X: Extended): Extended; { Log base 10 of X} function Log2(X: Extended): Extended; { Log base 2 of X } function LogN(Base, X: Extended): Extended; { Log base N of X } { Exponential functions } { IntPower: Raise base to an integral power. Fast. } function IntPower(Base: Extended; Exponent: Integer): Extended register; { Power: Raise base to any power. For fractional exponents, or exponents > MaxInt, base must be > 0. } function Power(Base, Exponent: Extended): Extended; { Miscellaneous Routines } { Frexp: Separates the mantissa and exponent of X. } procedure Frexp(X: Extended; var Mantissa: Extended; var Exponent: Integer) register; { Ldexp: returns X*2**P } function Ldexp(X: Extended; P: Integer): Extended register; { Ceil: Smallest integer >= X, X < MaxInt } function Ceil(X: Extended):Integer; { Floor: Largest integer <= X, X < MaxInt } function Floor(X: Extended): Integer; { Poly: Evaluates a uniform polynomial of one variable at value X. The coefficients are ordered in increasing powers of X: Coefficients[0] + Coefficients[1]*X + ... + Coefficients[N]*(X**N) } function Poly(X: Extended; const Coefficients: array of Double): Extended; { Statistical functions. Common commercial spreadsheet macro names for these statistical and financial functions are given in the comments preceding each function. } { Mean: Arithmetic average of values. (AVG): SUM / N } function Mean(const Data: array of Double): Extended; { Sum: Sum of values. (SUM) } function Sum(const Data: array of Double): Extended register; function SumOfSquares(const Data: array of Double): Extended; procedure SumsAndSquares(const Data: array of Double; var Sum, SumOfSquares: Extended) register; { MinValue: Returns the smallest signed value in the data array (MIN) } function MinValue(const Data: array of Double): Double; { MaxValue: Returns the largest signed value in the data array (MAX) } function MaxValue(const Data: array of Double): Double; { Standard Deviation (STD): Sqrt(Variance). aka Sample Standard Deviation } function StdDev(const Data: array of Double): Extended; { MeanAndStdDev calculates Mean and StdDev in one pass, which is 2x faster than calculating them separately. Less accurate when the mean is very large (> 10e7) or the variance is very small. } procedure MeanAndStdDev(const Data: array of Double; var Mean, StdDev: Extended); { Population Standard Deviation (STDP): Sqrt(PopnVariance). Used in some business and financial calculations. } function PopnStdDev(const Data: array of Double): Extended; { Variance (VARS): TotalVariance / (N1). aka Sample Variance } function Variance(const Data: array of Double): Extended; { Population Variance (VAR or VARP): TotalVariance/ N } function PopnVariance(const Data: array of Double): Extended; { Total Variance: SUM(i=1,N)[(X(i)  Mean)**2] } function TotalVariance(const Data: array of Double): Extended; { Norm: The Euclidean L2norm. Sqrt(SumOfSquares) } function Norm(const Data: array of Double): Extended; { MomentSkewKurtosis: Calculates the core factors of statistical analysis: the first four moments plus the coefficients of skewness and kurtosis. M1 is the Mean. M2 is the Variance. Skew reflects symmetry of distribution: M3 / (M2**(3/2)) Kurtosis reflects flatness of distribution: M4 / Sqr(M2) } procedure MomentSkewKurtosis(const Data: array of Double; var M1, M2, M3, M4, Skew, Kurtosis: Extended); { RandG produces random numbers with Gaussian distribution about the mean. Useful for simulating data with sampling errors. } function RandG(Mean, StdDev: Extended): Extended; { Financial functions. Standard set from Quattro Pro. Parameter conventions: From the point of view of A, amounts received by A are positive and amounts disbursed by A are negative (e.g. a borrower's loan repayments are regarded by the borrower as negative). Interest rates are per payment period. 11% annual percentage rate on a loan with 12 payments per year would be (11 / 100) / 12 = 0.00916667 } type TPaymentTime = (ptEndOfPeriod, ptStartOfPeriod); { Double Declining Balance (DDB) } function DoubleDecliningBalance(Cost, Salvage: Extended; Life, Period: Integer): Extended; { Future Value (FVAL) } function FutureValue(Rate: Extended; NPeriods: Integer; Payment, PresentValue: Extended; PaymentTime: TPaymentTime): Extended; { Interest Payment (IPAYMT) } function InterestPayment(Rate: Extended; Period, NPeriods: Integer; PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended; { Interest Rate (IRATE) } function InterestRate(NPeriods: Integer; Payment, PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended; { Internal Rate of Return. (IRR) Needs array of cash flows. } function InternalRateOfReturn(Guess: Extended; const CashFlows: array of Double): Extended; { Number of Periods (NPER) } function NumberOfPeriods(Rate, Payment, PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended; { Net Present Value. (NPV) Needs array of cash flows. } function NetPresentValue(Rate: Extended; const CashFlows: array of Double; PaymentTime: TPaymentTime): Extended; { Payment (PAYMT) } function Payment(Rate: Extended; NPeriods: Integer; PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended; { Period Payment (PPAYMT) } function PeriodPayment(Rate: Extended; Period, NPeriods: Integer; PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended; { Present Value (PVAL) } function PresentValue(Rate: Extended; NPeriods: Integer; Payment, FutureValue: Extended; PaymentTime: TPaymentTime): Extended; { Straight Line depreciation (SLN) } function SLNDepreciation(Cost, Salvage: Extended; Life: Integer): Extended; { SumofYearsDigits depreciation (SYD) } function SYDDepreciation(Cost, Salvage: Extended; Life, Period: Integer): 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 10byte number ranging from 3.4 * 10e4932 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 functions. 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 Buried in the System unit is a function called Slice: 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 indepth 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): type TResizeArr = array[0..0] of string; PResizeArr = ^TResizeArr; ... {============================================================================ Procedure which defines the dynarray. Note that the THandle and Pointer to the array are passed by reference. This is so that they may defined outside the scope of this proc. ============================================================================} procedure DefineDynArray(var h : THandle; {Handle to mem pointer} NumElements : LongInt; {Number of items in array} var PArr : PResizeArr); {Pointer to array struct} begin {Allocate Windows Global Heap memory} h := GlobalAlloc(GMEM_FIXED, NumElements * sizeof(TResizeArr)); PArr := GlobalLock(h); 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
dereference the pointer as follows: To pass the entire array into the Mean function as above, all we have to do is
dereference the entire array as follows: 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 it. unit parrmain; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, DB, DBTables, ComCtrls, Math, StdCtrls, ds_stats; type TResizeArr = Array[0..0] of Double; PResizeArr = ^TResizeArr; TForm1 = class(TForm) StatusBar1: TStatusBar; Button1: TButton; Table1: TTable; procedure Button1Click(Sender: TObject); private { Private declarations } PA : PResizeArr; Hndl : THandle; procedure DefineDynArray(var H : THandle; NumElem : LongInt; var PArr : PResizeArr); procedure FillArray; procedure SaveScreen; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.DefineDynArray(var H : THandle; NumElem : LongInt; var PArr : PResizeArr); begin H := GlobalAlloc(GMEM_FIXED, NumElem * SizeOf(TResizeArr)); PArr := GlobalLock(H); end; procedure TForm1.LoadArray; var tbl : TTable; recs : LongInt; begin tbl := TTable.Create(Application); //Create a TTable instance with tbl do begin //and set properties Active := False; DatabaseName := 'Primary'; TableName := 'Test'; TableType := ttParadox; Open; recs := RecordCount; //Get number of records in table DefineDynArray(Hndl, recs, PA); //Now, define our dynarray recs := 0; //Reset recs for reuse StatusBar1.SimpleText := 'Now filling array'; while NOT EOF do begin Application.ProcessMessages; //allow background processing try PA^[recs] := FieldByName('Charge').AsFloat; StatusBar1.SimpleText := 'Grabbed value of: ' + FloatToStr(PA^[recs]); StatusBar1.Invalidate; Inc(recs); Next; except GlobalUnlock(Hndl); GlobalFree(Hndl); Exit; end; end; //Pop up a message to show what was calculated. ShowMessage(FloatToStr(Mean(Slice(PA^, RecordCount)))); //pass the array using Slice GlobalUnlock(Hndl); //Unlock and Free memory and TTable instance. GlobalFree(Hndl); tbl.Free; end; end; procedure TForm1.Button1Click(Sender: TObject); begin LoadArray; end; 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. Copyright © 1997 Brendan V. Delumpa All Rights Reserved 
Copyright © The Delphi Corner 2001 All Rights Reserved 
