📄 math.pas
字号:
FADD // ST(1) := ST + ST(1); Pop ST
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);
asm // IN: EAX = ptr to Data
// EDX = High(Data) = Count - 1
// ECX = ptr to Sum
// Est. 17 clocks per loop, 4 items per loop = 4.5 clocks per data item
FLDZ // init Sum accumulator
PUSH ECX
MOV ECX, EDX
FLD ST(0) // init Sqr1 accum.
AND EDX, not 3
FLD ST(0) // init Sqr2 accum.
AND ECX, 3
FLD ST(0) // init/simulate last data item left in ST
SHL EDX, 3 // count * sizeof(Double) = count * 8
JMP @Vector.Pointer[ECX*4]
@Vector:
DD @@1
DD @@2
DD @@3
DD @@4
@@4: FADD // Sqr2 := Sqr2 + Sqr(Data4); Pop Data4
FLD qword ptr [EAX+EDX+24] // Load Data1
FADD ST(3),ST // Sum := Sum + Data1
FMUL ST,ST // Data1 := Sqr(Data1)
@@3: FLD qword ptr [EAX+EDX+16] // 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
@@2: FLD qword ptr [EAX+EDX+8] // 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
@@1: FLD qword ptr [EAX+EDX] // 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
SUB EDX,32
JNS @@4
FADD // Sqr2 := Sqr2 + Sqr(Data4); Pop Data4
POP ECX
FADD // Sqr1 := Sqr2 + Sqr1; Pop Sqr2
FXCH // Move Sum1 into ST(0)
MOV EAX, SumOfSquares
FSTP tbyte ptr [ECX] // Sum := Sum1; Pop Sum1
FSTP tbyte ptr [EAX] // SumOfSquares := Sum1; Pop Sum1
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(const 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
Result := 0;
if (Period < 1) or (Life < Period) or (Life < 1) or (Cost <= Salvage) then
Exit;
{depreciate everything in period 1 if life is only one or two periods}
if ( Life <= 2 ) then
begin
if ( Period = 1 ) then
DoubleDecliningBalance:=Cost-Salvage
else
DoubleDecliningBalance:=0; {all depreciation occurred in first period}
exit;
end;
Factor := 2.0 / Life;
DepreciatedVal := Cost * IntPower((1.0 - Factor), Period - 1);
{DepreciatedVal is Cost-(sum of previous depreciation results)}
Result := Factor * DepreciatedVal;
{Nominal computed depreciation for this period. The rest of the
function applies limits to this nominal value. }
{Only depreciate until total depreciation equals cost-salvage.}
if Result > DepreciatedVal - Salvage then
Result := DepreciatedVal - Salvage;
{No more depreciation after salvage value is reached. This is mostly a nit.
If Result is negative at this point, it's very close to zero.}
if Result < 0.0 then
Result := 0.0;
end;
function SLNDepreciation(const Cost, Salvage: Extended; Life: Integer): Extended;
{ Spreads depreciation linearly over life. }
begin
if Life < 1 then ArgError('SLNDepreciation');
Result := (Cost - Salvage) / Life
end;
function SYDDepreciation(const Cost, Salvage: Extended; Life, Period: Integer): Extended;
{ SYD = (cost - salvage) * (life - period + 1) / (life*(life + 1)/2) }
{ Note: life*(life+1)/2 = 1+2+3+...+life "sum of years"
The depreciation factor varies from life/sum_of_years in first period = 1
downto 1/sum_of_years in last period = life.
Total depreciation over life is cost-salvage.}
var
X1, X2: Extended;
begin
Result := 0;
if (Period < 1) or (Life < Period) or (Cost <= Salvage) then Exit;
X1 := 2 * (Life - Period + 1);
X2 := Life * (Life + 1);
Result := (Cost - Salvage) * X1 / X2
end;
{ Discounted cash flow functions. }
function InternalRateOfReturn(const 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;
ConditionP := K
end;
begin
InternalRateOfReturn := 0;
K := ConditionP(CashFlows);
if K < 0 then ArgError('InternalRateOfReturn');
if K = 0 then
begin
if Guess <= -1.0 then ArgError('InternalRateOfReturn');
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('InternalRateOfReturn');
if (Poly.Neg >= 0.0) or (Poly.Pos <= 0.0) then
begin
InternalRateOfReturn := -1.0;
Exit;
end;
with Poly do
Y := Ln(-Neg / Pos) / (DNeg / Neg - DPos / Pos);
T := T - Y;
if RelSmall(Y, T) then
begin
InternalRateOfReturn := Exp(-T) - 1.0;
Exit;
end
end;
ArgError('InternalRateOfReturn');
end;
function NetPresentValue(const Rate: Extended; const CashFlows: array of Double;
PaymentTime: TPaymentTime): Extended;
{ Caution: The sign of NPV is reversed from what would be expected for standard
cash flows!}
var
rr: Extended;
I: Integer;
begin
if Rate <= -1.0 then ArgError('NetPresentValue');
rr := 1/(1+Rate);
result := 0;
for I := High(CashFlows) downto Low(CashFlows) do
result := rr * result + CashFlows[I];
if PaymentTime = ptEndOfPeriod then result := rr * result;
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 "Compounding growth factor"
annuity(r, n) = (compound(r, n)-1) / r "Annuity growth factor"
Given future value fv, periodic payment pmt, present value pv and type
of payment (start, 1 , or end of period, 0) 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*(pmtTime+1/r)
pmt := -pv*r/(1 + r*pmtTime)
else
fv := -pmt(1+r*pmtTime)*A - pv*C
pv := (-pmt(1+r*pmtTime)*A - fv)/C
pmt := (-pv*C-fv)/((1+r*pmtTime)*A)
---------------}
function PaymentParts(Period, NPeriods: Integer; Rate, PresentValue,
FutureValue: Extended; PaymentTime: TPaymentTime; var IntPmt: Extended):
Extended;
var
Crn:extended; { =Compound(Rate,NPeriods) }
Crp:extended; { =Compound(Rate,Period-1) }
Arn:extended; { =Annuity2(...) }
begin
if Rate <= -1.0 then ArgError('PaymentParts');
Crp:=Compound(Rate,Period-1);
Arn:=Annuity2(Rate,NPeriods,PaymentTime,Crn);
IntPmt:=(FutureValue*(Crp-1)-PresentValue*(Crn-Crp))/Arn;
PaymentParts:=(-FutureValue-PresentValue)*Crp/Arn;
end;
function FutureValue(const Rate: Extended; NPeriods: Integer; const Payment,
PresentValue: Extended; PaymentTime: TPaymentTime): Extended;
var
Annuity, CompoundRN: Extended;
begin
if Rate <= -1.0 then ArgError('FutureValue');
Annuity := Annuity2(Rate, NPeriods, PaymentTime, CompoundRN);
if CompoundRN > 1.0E16 then ArgError('FutureValue');
FutureValue := -Payment * Annuity - PresentValue * CompoundRN
end;
function InterestPayment(const Rate: Extended; Period, NPeriods: Integer;
const PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
var
Crp:extended; { compound(rate,period-1)}
Crn:extended; { compound(rate,nperiods)}
Arn:extended; { annuityf(rate,nperiods)}
begin
if (Rate <= -1.0)
or (Period < 1) or (Period > NPeriods) then ArgError('InterestPayment');
Crp:=Compound(Rate,Period-1);
Arn:=Annuity2(Rate,Nperiods,PaymentTime,Crn);
InterestPayment:=(FutureValue*(Crp-1)-PresentValue*(Crn-Crp))/Arn;
end;
function InterestRate(NPeriods: Integer; const 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('InterestRate');
Pmt := Payment;
if PaymentTime = ptEndOfPeriod 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('InterestRate');
T := 0.0; { Guess at solution }
for Count := 1 to MaxIterations do
begin
EnT := Exp(NPeriods * T);
if {LostPrecision(EnT)} ent=(ent+1) 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;
InterestRate := Exp(T)-1.0;
Exit;
end
end;
ArgError('InterestRate');
end;
function NumberOfPeriods(const Rate: Extended; Payment: Extended;
const PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
{ If Rate = 0 then nper := -(pv + fv) / pmt
else cf := pv + pmt * (1 + rate*pmtTime) / rate
nper := LnXP1(-(pv + fv) / cf) / LnXP1(rate) }
var
PVRPP: Extended; { =PV*Rate+Payment } {"initial cash flow"}
T: Extended;
begin
if Rate <= -1.0 then ArgError('NumberOfPeriods');
{whenever both Payment and PaymentTime are given together, the PaymentTime has the effect
of modifying the effective Payment by the interest accrued on the Payment}
if ( PaymentTime=ptStartOfPeriod ) then
Payment:=Payment*(1+Rate);
{if the payment exactly matches the interest accrued periodically on the
presentvalue, then an infinite number of payments are going to be
required to effect a change from presentvalue to futurevalue. The
following catches that specific error where payment is exactly equal,
but opposite in sign to the interest on the present value. If PVRPP
("initial cash flow") is simply close to zero, the computation will
be numerically unstable, but not as likely to cause an error.}
PVRPP:=PresentValue*Rate+Payment;
if PVRPP=0 then ArgError('NumberOfPeriods');
{ 6.1E-5 approx= 2**-14 }
if ( ABS(Rate)<6.1E-5 ) then
Result:=-(PresentValue+FutureValue)/PVRPP
else
begin
{starting with the initial cash flow, each compounding period cash flow
should result in the current value approaching the final value. The
following test combines a number of simultaneous conditions to ensure
reasonableness of the cashflow before computing the NPER.}
T:= -(PresentValue+FutureValue)*Rate/PVRPP;
if T<=-1.0 then ArgError('NumberOfPeriods');
Result := LnXP1(T) / LnXP1(Rate)
end;
NumberOfPeriods:=Result;
end;
function Payment(Rate: Extended; NPeriods: Integer; const PresentValue,
FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
var
Annuity, CompoundRN: Extended;
begin
if Rate <= -1.0 then ArgError('Payment');
Annuity := Annuity2(Rate, NPeriods, PaymentTime, CompoundRN);
if CompoundRN > 1.0E16 then
Payment := -PresentValue * Rate / (1 + Integer(PaymentTime) * Rate)
else
Payment := (-PresentValue * CompoundRN - FutureValue) / Annuity
end;
function PeriodPayment(const Rate: Extended; Period, NPeriods: Integer;
const PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
var
Junk: Extended;
begin
if (Rate <= -1.0) or (Period < 1) or (Period > NPeriods) then ArgError('PeriodPayment');
PeriodPayment := PaymentParts(Period, NPeriods, Rate, PresentValue,
FutureValue, PaymentTime, Junk);
end;
function PresentValue(const Rate: Extended; NPeriods: Integer; const Payment,
FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
var
Annuity, CompoundRN: Extended;
begin
if Rate <= -1.0 then ArgError('PresentValue');
Annuity := Annuity2(Rate, NPeriods, PaymentTime, CompoundRN);
if CompoundRN > 1.0E16 then
PresentValue := -(Payment / Rate * Integer(PaymentTime) * Payment)
else
PresentValue := (-Payment * Annuity - FutureValue) / CompoundRN
end;
procedure ClearExceptions;
asm
fclex
end;
end.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -