home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Chip 1997 April
/
Chip_1997-04_cd.bin
/
prezent
/
cb
/
data.z
/
MATH.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1997-01-16
|
38KB
|
1,308 lines
{*******************************************************}
{ }
{ 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.