home *** CD-ROM | disk | FTP | other *** search
-
- {*******************************************************}
- { }
- { Delphi Runtime Library }
- { Math Unit }
- { }
- { Copyright (C) 1996 Borland International }
- { }
- {*******************************************************}
-
- unit Math;
-
- { This unit contains high-performance arithmetic, trigonometric, logorithmic,
- statistical and financial calculation routines which supplement the math
- routines that are part of the Delphi language or System unit. }
-
- {$N+,S-}
-
- interface
-
- uses SysUtils;
-
- const { Ranges of the IEEE floating point types, including denormals }
- MinSingle = 1.5e-45;
- MaxSingle = 3.4e+38;
- MinDouble = 5.0e-324;
- MaxDouble = 1.7e+308;
- MinExtended = 3.4e-4932;
- MaxExtended = 1.1e+4932;
- MinComp = -9.223372036854775807e+18;
- MaxComp = 9.223372036854775807e+18;
-
- {-----------------------------------------------------------------------
- References:
-
- 1) P.J. Plauger, "The Standard C Library", Prentice-Hall, 1992, Ch. 7.
- 2) W.J. Cody, Jr., and W. Waite, "Software Manual For the Elementary
- Functions", Prentice-Hall, 1980.
- 3) Namir Shammas, "C/C++ Mathematical Algorithms for Scientists and Engineers",
- McGraw-Hill, 1995, Ch 8.
- 4) H.T. Lau, "A Numerical Library in C for Scientists and Engineers",
- CRC Press, 1994, Ch. 6.
- 5) "Pentium(tm) Processor User's Manual, Volume 3: Architecture
- and Programming Manual", Intel, 1994
-
- All angle parameters and results of trig functions are in radians.
-
- 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 / (N-1). 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 L2-norm. 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;
-
- { Sum-of-Years-Digits depreciation (SYD) }
- function SYDDepreciation(Cost, Salvage: Extended; Life, Period: Integer): Extended;
-
- type
- EInvalidArgument = class(EMathError) end;
-
- implementation
-
- function Annuity2(R: Extended; N: Integer; PaymentTime: TPaymentTime;
- var CompoundRN: Extended): Extended; Forward;
- function Compound(R: Extended; N: Integer): Extended; Forward;
- function RelSmall(X, Y: Extended): Boolean; Forward;
-
- type
- TPoly = record
- Neg, Pos, DNeg, DPos: Extended
- end;
-
- const
- MaxIterations = 15;
-
- procedure ArgError;
- begin
- raise EInvalidArgument.Create('');
- end;
-
- function DegToRad(Degrees: Extended): Extended; { Radians := Degrees * PI / 180 }
- begin
- Result := Degrees * (PI / 180);
- end;
-
- function RadToDeg(Radians: Extended): Extended; { Degrees := Radians * 180 / PI }
- begin
- Result := Radians * (180 / PI);
- end;
-
- function GradToRad(Grads: Extended): Extended; { Radians := Grads * PI / 200 }
- begin
- Result := Grads * (PI / 200);
- end;
-
- function RadToGrad(Radians: Extended): Extended; { Grads := Radians * 200 / PI}
- begin
- Result := Radians * (200 / PI);
- end;
-
- function CycleToRad(Cycles: Extended): Extended; { Radians := Cycles * 2PI }
- begin
- Result := Cycles * (2 * PI);
- end;
-
- function RadToCycle(Radians: Extended): Extended;{ Cycles := Radians / 2PI }
- begin
- Result := Radians / (2 * PI);
- end;
-
- function LnXP1(X: Extended): Extended;
- { Return ln(1 + X). Accurate for X near 0. }
- asm
- FLDLN2
- MOV AX,WORD PTR X+8 { exponent }
- FLD X
- CMP AX,$3FFD { .4225 }
- JB @@1
- FLD1
- FADD
- FYL2X
- JMP @@2
- @@1:
- FYL2XP1
- @@2:
- FWAIT
- end;
-
- { Invariant: Y >= 0 & Result*X**Y = X**I. Init Y = I and Result = 1. }
- {function IntPower(X: Extended; I: Integer): Extended;
- var
- Y: Integer;
- begin
- Y := Abs(I);
- Result := 1.0;
- while Y > 0 do begin
- while not Odd(Y) do
- begin
- Y := Y shr 1;
- X := X * X
- end;
- Dec(Y);
- Result := Result * X
- end;
- if I < 0 then Result := 1.0 / Result
- end;
- }
- function IntPower(Base: Extended; Exponent: Integer): Extended;
- asm
- mov ecx, eax
- cdq
- fld1 { Result := 1 }
- xor eax, edx
- sub eax, edx { eax := Abs(Exponent) }
- jz @@3
- fld Base
- jmp @@2
- @@1: fmul ST, ST { X := Base * Base }
- @@2: shr eax,1
- jnc @@1
- fmul ST(1),ST { Result := Result * X }
- jnz @@1
- fstp st { pop X from FPU stack }
- cmp ecx, 0
- jge @@3
- fld1
- fdivrp { Result := 1 / Result }
- @@3:
- fwait
- end;
-
- function Compound(R: Extended; N: Integer): Extended;
- { Return (1 + R)**N. }
- begin
- Result := IntPower(1.0 + R, N)
- end;
-
- function Annuity2(R: Extended; N: Integer; PaymentTime: TPaymentTime;
- var CompoundRN: Extended): Extended;
- { Set CompoundRN to Compound(R, N),
- return (1 + Rate*PaymentTime) * (1 - Compound(R, N)) / R }
- begin
- if R = 0.0 then
- begin
- CompoundRN := 1.0;
- Result := -N
- end
- else
- begin
- { 6.1E-5 approx= 2**-14 }
- if Abs(R) < 6.1E-5 then
- begin
- CompoundRN := Exp(N * LnXP1(R));
- Result := (CompoundRN - 1.0) / -R;
- end
- else
- begin
- CompoundRN := Compound(R, N);
- Result := (1 - CompoundRN) / R
- end;
- // if PaymentTime = ptEndOfPeriod then Result := Result + R
- if PaymentTime = ptStartOfPeriod then Result := Result * (1 + R);
- end
- end;
-
- procedure PolyX(const A: array of Double; X: Extended; var Poly: TPoly);
- { Compute A[0] + A[1]*X + ... + A[N]*X**N and X * its derivative.
- Accumulate positive and negative terms separately. }
- var
- I: Integer;
- Neg, Pos, DNeg, DPos: Extended;
- begin
- Neg := 0.0;
- Pos := 0.0;
- DNeg := 0.0;
- DPos := 0.0;
- for I := Low(A) to High(A) do
- begin
- DNeg := X * DNeg + Neg;
- Neg := Neg * X;
- DPos := X * DPos + Pos;
- Pos := Pos * X;
- if A[I] >= 0.0 then Pos := Pos + A[I] else Neg := Neg + A[I]
- end;
- Poly.Neg := Neg;
- Poly.Pos := Pos;
- Poly.DNeg := DNeg * X;
- Poly.DPos := DPos * X;
- end;
-
- function RelSmall(X, Y: Extended): Boolean;
- { Returns True if X is small relative to Y }
- const
- C1: Double = 1E-15;
- C2: Double = 1E-12;
- begin
- Result := Abs(X) < (C1 + C2 * Abs(Y))
- end;
-
- { Math functions. }
-
- function ArcCos(X: Extended): Extended;
- begin
- Result := ArcTan2(Sqrt(1 - X*X), X);
- end;
-
- function ArcSin(X: Extended): Extended;
- begin
- Result := ArcTan2(X, Sqrt(1 - X*X))
- end;
-
- function ArcTan2(Y, X: Extended): Extended;
- asm
- FLD Y
- FLD X
- FPATAN
- FWAIT
- end;
-
- function Tan(X: Extended): Extended;
- { Tan := Sin(X) / Cos(X) }
- asm
- FLD X
- FPTAN
- FSTP ST(0) { FPTAN pushes 1.0 after result }
- FWAIT
- end;
-
- function CoTan(X: Extended): Extended;
- { CoTan := Cos(X) / Sin(X) = 1 / Tan(X) }
- asm
- FLD X
- FPTAN
- FDIVRP
- FWAIT
- end;
-
- function Hypot(X, Y: Extended): Extended;
- { formula: Sqrt(X*X + Y*Y)
- implemented as: |Y|*Sqrt(1+Sqr(X/Y)), |X| < |Y| for greater precision
- var
- Temp: Extended;
- begin
- X := Abs(X);
- Y := Abs(Y);
- if X > Y then
- begin
- Temp := X;
- X := Y;
- Y := Temp;
- end;
- if X = 0 then
- Result := Y
- else // Y > X, X <> 0, so Y > 0
- Result := Y * Sqrt(1 + Sqr(X/Y));
- end;
- }
- asm
- FLD Y
- FABS
- FLD X
- FABS
- FCOM
- FNSTSW AX
- TEST AH,$F
- JNZ @@1 // if ST > ST(1) then swap
- FXCH ST(1) // put larger number in ST(1)
- @@1: FLDZ
- FCOMP
- FNSTSW AX
- TEST AH,$4 // if ST = 0, return ST(1)
- JZ @@2
- FSTP ST // eat ST(0)
- JMP @@3
- @@2: FDIV ST,ST(1) // ST := ST / ST(1)
- FMUL ST,ST // ST := ST * ST
- FLD1
- FADD // ST := ST + 1
- FSQRT // ST := Sqrt(ST)
- FMUL // ST(1) := ST * ST(1); Pop ST
- @@3: FWAIT
- end;
-
-
- procedure SinCos(Theta: Extended; var Sin, Cos: Extended);
- asm
- FLD Theta
- FSINCOS
- FSTP tbyte ptr [edx] // Cos
- FSTP tbyte ptr [eax] // Sin
- FWAIT
- end;
-
- { Extract exponent and mantissa from X }
- procedure Frexp(X: Extended; var Mantissa: Extended; var Exponent: Integer);
- { Mantissa ptr in EAX, Exponent ptr in EDX }
- asm
- FLD X
- PUSH EAX
- MOV dword ptr [edx], 0 { if X = 0, return 0 }
-
- FTST
- FSTSW AX
- FWAIT
- SAHF
- JZ @@Done
-
- FXTRACT // ST(1) = exponent, (pushed) ST = fraction
- FXCH
-
- // The FXTRACT instruction normalizes the fraction 1 bit higher than
- // wanted for the definition of frexp() so we need to tweak the result
- // by scaling the fraction down and incrementing the exponent.
-
- FISTP dword ptr [edx]
- FLD1
- FCHS
- FXCH
- FSCALE // scale fraction
- INC dword ptr [edx] // exponent biased to match
- FSTP ST(1) // discard -1, leave fraction as TOS
-
- @@Done:
- POP EAX
- FSTP tbyte ptr [eax]
- FWAIT
- end;
-
- function Ldexp(X: Extended; P: Integer): Extended;
- { Result := X * (2^P) }
- asm
- PUSH EAX
- FILD dword ptr [ESP]
- FLD X
- FSCALE
- POP EAX
- FSTP ST(1)
- FWAIT
- end;
-
- function Ceil(X: Extended): Integer;
- begin
- Result := Trunc(X);
- if Frac(X) > 0 then
- Inc(Result);
- end;
-
- function Floor(X: Extended): Integer;
- begin
- Result := Trunc(X);
- if Frac(X) < 0 then
- Dec(Result);
- end;
-
- { Conversion of bases: Log.b(X) = Log.a(X) / Log.a(b) }
-
- function Log10(X: Extended): Extended;
- { Log.10(X) := Log.2(X) * Log.10(2) }
- asm
- FLDLG2 { Log base ten of 2 }
- FLD X
- FYL2X
- FWAIT
- end;
-
- function Log2(X: Extended): Extended;
- asm
- FLD1
- FLD X
- FYL2X
- FWAIT
- end;
-
- function LogN(Base, X: Extended): Extended;
- { Log.N(X) := Log.2(X) / Log.2(N) }
- asm
- FLD1
- FLD X
- FYL2X
- FLD1
- FLD Base
- FYL2X
- FDIV
- FWAIT
- end;
-
- function Poly(X: Extended; const Coefficients: array of Double): Extended;
- { Horner's method }
- var
- I: Integer;
- begin
- Result := Coefficients[High(Coefficients)];
- for I := High(Coefficients)-1 downto Low(Coefficients) do
- Result := Result * X + Coefficients[I];
- end;
-
- function Power(Base, Exponent: Extended): Extended;
- begin
- { Make special cases of Exponent = 0 and Exponent = integer.
- Error if Base < 0 and Exponent not integer. }
- if Exponent = 0.0 then
- Result := 1.0 { By fiat, 0**0 = 1 }
- else if (Frac(Exponent) = 0.0) and (Exponent < MaxInt) then
- Result := IntPower(Base, Trunc(Exponent))
- else
- begin
- if Base < 0.0 then ArgError;
- Result := Exp(Exponent * Ln(Base))
- end
- end;
-
- { Hyperbolic functions }
-
- function CoshSinh(X: Extended; Factor: Double): Extended;
- begin
- Result := Exp(X) / 2;
- Result := Result + Factor / Result;
- end;
-
- function Cosh(X: Extended): Extended;
- begin
- Result := CoshSinh(X, 0.25)
- end;
-
- function Sinh(X: Extended): Extended;
- begin
- Result := CoshSinh(X, -0.25)
- end;
-
- const
- MaxTanhDomain = 5678.22249441322; // Ln(MaxExtended)/2
-
- function Tanh(X: Extended): Extended;
- begin
- if X > MaxTanhDomain then
- Result := 1.0
- else if X < -MaxTanhDomain then
- Result := -1.0
- else
- begin
- Result := Exp(X);
- Result := Result * Result;
- Result := (Result - 1.0) / (Result + 1.0)
- end;
- end;
-
- function ArcCosh(X: Extended): Extended;
- begin
- if X <= 1.0 then
- Result := 0.0
- else if X > 1.0e10 then
- Result := Ln(2) + Ln(X)
- else
- Result := Ln(X + Sqrt((X - 1.0) * (X + 1.0)));
- end;
-
- function ArcSinh(X: Extended): Extended;
- var
- Neg: Boolean;
- begin
- if X = 0 then
- Result := 0
- else
- begin
- Neg := (X < 0);
- X := Abs(X);
- if X > 1.0e10 then
- Result := Ln(2) + Ln(X)
- else
- begin
- Result := X*X;
- Result := LnXP1(X + Result / (1 + Sqrt(1 + Result)));
- end;
- if Neg then Result := -Result;
- end;
- end;
-
- function ArcTanh(X: Extended): Extended;
- var
- Neg: Boolean;
- begin
- if X = 0 then
- Result := 0
- else
- begin
- Neg := (X < 0);
- X := Abs(X);
- if X >= 1 then
- Result := MaxExtended
- else
- Result := 0.5 * LnXP1((2.0 * X) / (1.0 - X));
- if Neg then Result := -Result;
- end;
- end;
-
- { Statistical functions }
-
- function Mean(const Data: array of Double): Extended;
- var
- I: Integer;
- begin
- Result := SUM(Data) / (High(Data) - Low(Data) + 1)
- end;
-
- function MinValue(const Data: array of Double): Double;
- var
- I: Integer;
- begin
- Result := Data[Low(Data)];
- for I := Low(Data) + 1 to High(Data) do
- if Result > Data[I] then
- Result := Data[I];
- end;
-
- function MaxValue(const Data: array of Double): Double;
- var
- I: Integer;
- begin
- Result := Data[Low(Data)];
- for I := Low(Data) + 1 to High(Data) do
- if Result < Data[I] then
- Result := Data[I];
- end;
-
- procedure MeanAndStdDev(const Data: array of Double; var Mean, StdDev: Extended);
- var
- Sums, Squares: Extended;
- N: Integer;
- begin
- SumsAndSquares(Data, Sums, Squares);
- N := High(Data)- Low(Data) + 1;
- Mean := Sums / N;
- StdDev := Sqrt((Squares - Sqr(Sums) / N) / (N - 1));
- end;
-
- procedure MomentSkewKurtosis(const Data: array of Double;
- var M1, M2, M3, M4, Skew, Kurtosis: Extended);
- var
- Sum, SumSquares, SumCubes, SumQuads, OverN, Accum, M1Sqr, S2N, S3N: Extended;
- I: Integer;
- begin
- OverN := 1 / (High(Data) - Low(Data) + 1);
- Sum := 0;
- SumSquares := 0;
- SumCubes := 0;
- SumQuads := 0;
- for I := Low(Data) to High(Data) do
- begin
- Sum := Sum + Data[I];
- Accum := Sqr(Data[I]);
- SumSquares := SumSquares + Accum;
- Accum := Accum*Data[I];
- SumCubes := SumCubes + Accum;
- SumQuads := SumQuads + Accum*Data[I];
- end;
- M1 := Sum * OverN;
- M1Sqr := Sqr(M1);
- S2N := SumSquares * OverN;
- S3N := SumCubes * OverN;
- M2 := S2N - M1Sqr;
- M3 := S3N - (M1 * 3 * S2N) + 2*M1Sqr*M1;
- M4 := (SumQuads * OverN) - (M1 * 4 * S3N) + (M1Sqr*6*S2N - 3*Sqr(M1Sqr));
- Skew := M3 * Power(M2, -3/2); // = M3 / Power(M2, 3/2)
- Kurtosis := M4 / Sqr(M2);
- end;
-
- function Norm(const Data: array of Double): Extended;
- begin
- Result := Sqrt(SumOfSquares(Data));
- end;
-
- function PopnStdDev(const Data: array of Double): Extended;
- begin
- Result := Sqrt(PopnVariance(Data))
- end;
-
- function PopnVariance(const Data: array of Double): Extended;
- begin
- Result := TotalVariance(Data) / (High(Data) - Low(Data) + 1)
- end;
-
- function RandG(Mean, StdDev: Extended): Extended;
- { Marsaglia-Bray algorithm }
- var
- U1, S2: Extended;
- begin
- repeat
- U1 := 2*Random - 1;
- S2 := Sqr(U1) + Sqr(2*Random-1);
- until S2 < 1;
- Result := Sqrt(-2*Ln(S2)/S2) * U1 * StdDev + Mean;
- end;
-
- function StdDev(const Data: array of Double): Extended;
- begin
- Result := Sqrt(Variance(Data))
- end;
-
- function SUM(const Data: array of Double): Extended;
- {var
- I: Integer;
- begin
- Result := 0.0;
- for I := Low(Data) to High(Data) do
- Result := Result + Data[I]
- end; }
- asm // IN: EAX = ptr to Data, EDX = High(Data) = Count - 1
- // Uses 2 accumulators to minimize read-after-write delays
- // Est. 4 clocks per loop, 2 items per loop = 2 clocks per datum
- FLD qword ptr [EAX] // Init first accumulator
- INC EDX
- ADD EAX,8
- SHR EDX,1
- JC @@OddCount
- JZ @@End
- JMP @@1
- @@OddCount:
- FADD qword ptr [EAX]
- ADD EAX,8
- @@1: FLDZ // Init second accumulator
- @@2: FADD qword ptr [EAX] // 1
- FXCH ST(1) // 0
- FADD qword ptr [EAX+8] // 1
- FXCH ST(1) // 0
- ADD EAX,16 // 1
- DEC EDX // 0
- JNZ @@2 // 1
- FADD // ST(1) := ST + ST(1); Pop ST
- @@End:
- FWAIT
- end;
-
- function SumOfSquares(const Data: array of Double): Extended;
- var
- I: Integer;
- begin
- Result := 0.0;
- for I := Low(Data) to High(Data) do
- Result := Result + Sqr(Data[I]);
- end;
-
- procedure SumsAndSquares(const Data: array of Double; var Sum, SumOfSquares: Extended);
- {var
- I: Integer;
- begin
- Sum := 0;
- SumOfSquares := 0;
- for I := Low(Data) to High(Data) do
- begin
- Sum := Sum + Data[I];
- SumOfSquares := SumOfSquares + Data[I]*Data[I];
- end;
- end; }
- asm // IN: EAX = ptr to Data
- // EDX = High(Data) = Count - 1
- // ECX = ptr to Sum
- // Est. 18 clocks per loop, 4 items per loop = 4.5 clocks per data item
- PUSH EBX
- INC EDX
- FLDZ
- MOV EBX,EDX
- FLD ST(0)
- AND EBX,3
- JZ @@1
- JMP @@P1
- @@P0: FADD
- @@P1: FLD qword ptr [EAX]
- FADD ST(2),ST
- FMUL ST,ST
- ADD EAX,8
- DEC EBX
- JNZ @@P0
- FADD
- @@1: SHR EDX,2
- JZ @@End
- FLDZ // FPU stack: Sqr2, Sqr1, Sum
- JMP @@3
- @@2: FADD // Sqr2 := Sqr2 + Sqr(Data4); Pop Data4
- @@3: FLD qword ptr [EAX] // Load Data1
- FADD ST(3),ST // Sum := Sum + Data1
- FMUL ST,ST // Data1 := Sqr(Data1)
- FLD qword ptr [EAX+8] // Load Data2
- FADD ST(4),ST // Sum := Sum + Data2
- FMUL ST,ST // Data2 := Sqr(Data2)
- FXCH // Move Sqr(Data1) into ST(0)
- FADDP ST(3),ST // Sqr1 := Sqr1 + Sqr(Data1); Pop Data1
- FLD qword ptr [EAX+16] // Load Data3
- FADD ST(4),ST // Sum := Sum + Data3
- FMUL ST,ST // Data3 := Sqr(Data3)
- FXCH // Move Sqr(Data2) into ST(0)
- FADDP ST(3),ST // Sqr1 := Sqr1 + Sqr(Data2); Pop Data2
- FLD qword ptr [EAX+24] // Load Data4
- FADD ST(4),ST // Sum := Sum + Data4
- FMUL ST,ST // Sqr(Data4)
- FXCH // Move Sqr(Data3) into ST(0)
- FADDP ST(3),ST // Sqr1 := Sqr1 + Sqr(Data3); Pop Data3
- ADD EAX,32
- DEC EDX
- JNZ @@2
- FADD // Sqr2 := Sqr2 + Sqr(Data4); Pop Data4
- FADD // Sqr1 := Sqr2 + Sqr1; Pop Sqr2
- @@End:
- MOV EAX, SumOfSquares
- FXCH // Move Sum1 into ST(0)
- FSTP tbyte ptr [ECX] // Sum := Sum1; Pop Sum1
- FSTP tbyte ptr [EAX] // SumOfSquares := Sum1; Pop Sum1
- POP EBX
- FWAIT
- end;
-
- function TotalVariance(const Data: array of Double): Extended;
- var
- Sum, SumSquares: Extended;
- begin
- SumsAndSquares(Data, Sum, SumSquares);
- Result := SumSquares - Sqr(Sum)/(High(Data) - Low(Data) + 1);
- end;
-
- function Variance(const Data: array of Double): Extended;
- begin
- Result := TotalVariance(Data) / (High(Data) - Low(Data))
- end;
-
-
- { Depreciation functions. }
-
- function DoubleDecliningBalance(Cost, Salvage: Extended; Life, Period: Integer): Extended;
- { dv := cost * (1 - 2/life)**(period - 1)
- DDB = (2/life) * dv
- if DDB > dv - salvage then DDB := dv - salvage
- if DDB < 0 then DDB := 0
- }
- var
- DepreciatedVal, Factor: Extended;
- begin
- if (Period < 1) or (Life < Period) or (Life <= 2) or (Cost < Salvage) then
- ArgError;
- Factor := 2.0 / Life;
- DepreciatedVal := IntPower(Cost * (1.0 - Factor), Period - 1);
- Result := Factor * DepreciatedVal;
- if Result > DepreciatedVal - Salvage then
- Result := DepreciatedVal - Salvage;
- if Result < 0.0 then
- Result := 0.0
- end;
-
- function SLNDepreciation(Cost, Salvage: Extended; Life: Integer): Extended;
- { Spreads depreciation linearly over life. }
- begin
- if Life < 1 then ArgError;
- Result := (Cost - Salvage) / Life
- end;
-
- function SYDDepreciation(Cost, Salvage: Extended; Life, Period: Integer): Extended;
- { SYD = (cost - salvage) * (life - period + 1) / (life*(life + 1)/2) }
- var
- X1, X2: Extended;
- begin
- if (Period < 1) or (Life < Period) then ArgError;
- X1 := 2 * (Life - Period + 1);
- X2 := Life * (Life + 1);
- Result := (Cost - Salvage) * X1 / X2
- end;
-
- { Discounted cash flow functions. }
-
- function InternalRateOfReturn(Guess: Extended; const CashFlows: array of Double): Extended;
- {
- Use Newton's method to solve NPV = 0, where NPV is a polynomial in
- x = 1/(1+rate). Split the coefficients into negative and postive sets:
- neg + pos = 0, so pos = -neg, so -neg/pos = 1
- Then solve:
- log(-neg/pos) = 0
-
- Let t = log(1/(1+r) = -LnXP1(r)
- then r = exp(-t) - 1
- Iterate on t, then use the last equation to compute r.
- }
- var
- T, Y: Extended;
- Poly: TPoly;
- K, Count: Integer;
-
- function ConditionP(const CashFlows: array of Double): Integer;
- { Guarantees existence and uniqueness of root. The sign of payments
- must change exactly once, the net payout must be always > 0 for
- first portion, then each payment must be >= 0.
- Returns: 0 if condition not satisfied, > 0 if condition satisfied
- and this is the index of the first value considered a payback. }
- var
- X: Double;
- I, K: Integer;
- begin
- K := High(CashFlows);
- while (K >= 0) and (CashFlows[K] >= 0.0) do Dec(K);
- Inc(K);
- if K > 0 then begin
- X := 0.0;
- I := 0;
- while I < K do begin
- X := X + CashFlows[I];
- if X >= 0.0 then begin
- K := 0;
- Break
- end;
- Inc(I)
- end
- end;
- Result := K
- end;
-
- begin
- Result := 0;
- K := ConditionP(CashFlows);
- if K < 0 then ArgError;
- if K = 0 then
- begin
- if Guess <= -1.0 then ArgError;
- T := -LnXP1(Guess)
- end else
- T := 0.0;
- for Count := 1 to MaxIterations do
- begin
- PolyX(CashFlows, Exp(T), Poly);
- if Poly.Pos <= Poly.Neg then ArgError;
- if (Poly.Neg >= 0.0) or (Poly.Pos <= 0.0) then
- begin
- Result := -1.0;
- Exit;
- end;
- with Poly do
- Y := Ln(-Neg / Pos) / (DNeg / Neg - DPos / Pos);
- T := T - Y;
- if RelSmall(Y, T) then
- begin
- Result := Exp(-T) - 1.0;
- Exit;
- end
- end;
- ArgError;
- end;
-
- function NetPresentValue(Rate: Extended; const CashFlows: array of Double;
- PaymentTime: TPaymentTime): Extended;
- var
- X, Y: Extended;
- I: Integer;
- begin
- if Rate <= -1.0 then ArgError;
- X := 1.0 / (1.0 + Rate);
- Y := CashFlows[High(CashFlows)];
- for I := High(CashFlows) - 1 downto Low(CashFlows) do
- Y := X * Y + CashFlows[I];
- if PaymentTime = ptStartOfPeriod then Y := X * Y;
- Result := Y;
- end;
-
- { Annuity functions. }
-
- {---------------
- 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).
-
- Given interest rate r, number of periods n:
- compound(r, n) = (1 + r)**n
- annuity(r, n) = (1 - compound(r, n)) / r
-
- Given future value fv, periodic payment pmt, present value pv and type
- of payment (start or end of period) pmtTime, financial variables satisfy:
-
- fv = pmt*(1 * r*pmtTime)*annuity(r, n) - pv*compound(r, n)
-
- For fv, pv, pmt:
-
- C := compound(r, n)
- A := (1 + r*pmtTime)*annuity(r, n)
- Compute both at once in Annuity2.
-
- if C > 1E16 then A = -C/r, so:
- fv := meaningless
- pv := -(pmt/r + pmt*pmtTime)
- pmt := -pv*r/(1 + r*pmtTime)
- else
- fv := pmt*A - pv*C
- pv := (pmt*A - fv)/C
- pmt := (pv*C + fv)/A
- ---------------}
-
- function PaymentParts(Period, NPeriods: Integer; Rate, PresentValue,
- FutureValue: Extended; PaymentTime: TPaymentTime; var IntPmt: Extended):
- Extended;
- var
- Pmt, Rate1, T1, T2, T3: Extended;
- M: Integer;
-
- function Annuity(R: Extended; N: Integer): Extended;
- begin
- Result := (1 - Compound(R, N)) / R
- end;
-
- begin
- M := Period;
- Rate1 := 0.0;
- if PaymentTime = ptEndOfPeriod then
- begin
- Inc(M);
- Rate1 := Rate
- end;
- T1 := (1 + Rate1) * Annuity(Rate, NPeriods);
- T2 := (1 + Rate1) * Annuity(Rate, M);
- T3 := (1 + Rate1) * Annuity(Rate, NPeriods - M);
- Pmt := (FutureValue + PresentValue * Compound(Rate, NPeriods)) / T1;
- IntPmt := Rate *
- (FutureValue * Compound(Rate, M) - PresentValue * T2 * T3) / T1;
- Result := Pmt - IntPmt
- end;
-
- function FutureValue(Rate: Extended; NPeriods: Integer; Payment, PresentValue:
- Extended; PaymentTime: TPaymentTime): Extended;
- var
- Annuity, CompoundRN: Extended;
- begin
- Annuity := Annuity2(Rate, NPeriods, PaymentTime, CompoundRN);
- if CompoundRN > 1.0E16 then ArgError;
- Result := Payment * Annuity - PresentValue * CompoundRN
- end;
-
- function InterestPayment(Rate: Extended; Period, NPeriods: Integer; PresentValue,
- FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
- begin
- if (Period < 1) or (Period > NPeriods) then ArgError;
- PaymentParts(Period, NPeriods, Rate, PresentValue, FutureValue, PaymentTime, Result);
- end;
-
- function InterestRate(NPeriods: Integer;
- Payment, PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
- {
- Given:
- First and last payments are non-zero and of opposite signs.
- Number of periods N >= 2.
- Convert data into cash flow of first, N-1 payments, last with
- first < 0, payment > 0, last > 0.
- Compute the IRR of this cash flow:
- 0 = first + pmt*x + pmt*x**2 + ... + pmt*x**(N-1) + last*x**N
- where x = 1/(1 + rate).
- Substitute x = exp(t) and apply Newton's method to
- f(t) = log(pmt*x + ... + last*x**N) / -first
- which has a unique root given the above hypotheses.
- }
- var
- X, Y, Z, First, Pmt, Last, T, ET, EnT, ET1: Extended;
- Count: Integer;
- Reverse: Boolean;
-
- function LostPrecision(X: Extended): Boolean;
- asm
- XOR EAX, EAX
- MOV BX,WORD PTR X+8
- INC EAX
- AND EBX, $7FF0
- JZ @@1
- CMP EBX, $7FF0
- JE @@1
- XOR EAX,EAX
- @@1:
- end;
-
- begin
- Result := 0;
- if NPeriods <= 0 then ArgError;
- Pmt := Payment;
- if PaymentTime = ptStartOfPeriod then
- begin
- X := PresentValue;
- Y := FutureValue + Payment
- end
- else
- begin
- X := PresentValue + Payment;
- Y := FutureValue
- end;
- First := X;
- Last := Y;
- Reverse := False;
- if First * Payment > 0.0 then
- begin
- Reverse := True;
- T := First;
- First := Last;
- Last := T
- end;
- if first > 0.0 then
- begin
- First := -First;
- Pmt := -Pmt;
- Last := -Last
- end;
- if (First = 0.0) or (Last < 0.0) then ArgError;
- T := 0.0; { Guess at solution }
- for Count := 1 to MaxIterations do
- begin
- EnT := Exp(NPeriods * T);
- if LostPrecision(EnT) then
- begin
- Result := -Pmt / First;
- if Reverse then
- Result := Exp(-LnXP1(Result)) - 1.0;
- Exit;
- end;
- ET := Exp(T);
- ET1 := ET - 1.0;
- if ET1 = 0.0 then
- begin
- X := NPeriods;
- Y := X * (X - 1.0) / 2.0
- end
- else
- begin
- X := ET * (Exp((NPeriods - 1) * T)-1.0) / ET1;
- Y := (NPeriods * EnT - ET - X * ET) / ET1
- end;
- Z := Pmt * X + Last * EnT;
- Y := Ln(Z / -First) / ((Pmt * Y + Last * NPeriods *EnT) / Z);
- T := T - Y;
- if RelSmall(Y, T) then
- begin
- if not Reverse then T := -T;
- Result := Exp(T)-1.0;
- Exit;
- end
- end;
- ArgError;
- end;
-
- function NumberOfPeriods(Rate, Payment, PresentValue, FutureValue: Extended;
- PaymentTime: TPaymentTime): Extended;
- { If Rate = 0 then nper := -(pv + fv) / pmt
- else t := pv + pmt * (1 + rate*pmtTime) / rate
- nper := LnXP1(-(pv + vf) / t) / LnXP1(rate)
- }
- var
- T: Extended;
- begin
- if Frac(Rate) = 0.0 then
- Result := -(PresentValue + FutureValue) / Payment
- else
- begin
- T := PresentValue + Payment * (1 + Integer(PaymentTime) * Rate) / Rate;
- Result := LnXP1(-(PresentValue + FutureValue) / T) / LnXP1(Rate)
- end
- end;
-
- function Payment(Rate: Extended; NPeriods: Integer; PresentValue, FutureValue:
- Extended; PaymentTime: TPaymentTime): Extended;
- var
- Annuity, CompoundRN: Extended;
- begin
- Annuity := Annuity2(Rate, NPeriods, PaymentTime, CompoundRN);
- if CompoundRN > 1.0E16 then
- Result := -PresentValue * Rate / (1 + Integer(PaymentTime) * Rate)
- else
- Result := (PresentValue * CompoundRN + FutureValue) / Annuity
- end;
-
- function PeriodPayment(Rate: Extended; Period, NPeriods: Integer;
- PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
- var
- Junk: Extended;
- begin
- if (Period < 1) or (Period > NPeriods) then ArgError;
- Result := PaymentParts(Period, NPeriods, Rate, PresentValue, FutureValue,
- PaymentTime, Junk);
- end;
-
- function PresentValue(Rate: Extended; NPeriods: Integer; Payment, FutureValue:
- Extended; PaymentTime: TPaymentTime): Extended;
- var
- Annuity, CompoundRN: Extended;
- begin
- Annuity := Annuity2(Rate, NPeriods, PaymentTime, CompoundRN);
- if CompoundRN > 1.0E16 then
- Result := -(Payment / Rate * Integer(PaymentTime) * Payment)
- else
- Result := (Payment * Annuity - FutureValue) / CompoundRN
- end;
-
- end.
-