fpspreadsheet: Add formula financial function NPER to user-defined formula demo.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3291 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-07-06 19:13:05 +00:00
parent 486e6b630c
commit 803c910ac6
2 changed files with 158 additions and 75 deletions

View File

@ -4,194 +4,204 @@
<PathDelim Value="\"/>
<Version Value="9"/>
<BuildModes Active="Debug"/>
<Units Count="7">
<Units Count="8">
<Unit0>
<Filename Value="test_formula_func.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="test_formula_func"/>
<IsVisibleTab Value="True"/>
<TopLine Value="295"/>
<CursorPos X="19" Y="324"/>
<UsageCount Value="27"/>
<TopLine Value="373"/>
<CursorPos X="30" Y="209"/>
<UsageCount Value="37"/>
<Bookmarks Count="1">
<Item0 X="21" Y="280" ID="1"/>
<Item0 X="21" Y="325" ID="1"/>
</Bookmarks>
<Loaded Value="True"/>
</Unit0>
<Unit1>
<Filename Value="..\..\fpspreadsheet.pas"/>
<UnitName Value="fpspreadsheet"/>
<EditorIndex Value="4"/>
<EditorIndex Value="5"/>
<TopLine Value="1216"/>
<CursorPos X="9" Y="1128"/>
<UsageCount Value="13"/>
<UsageCount Value="18"/>
<Loaded Value="True"/>
</Unit1>
<Unit2>
<Filename Value="..\..\fpsfunc.pas"/>
<UnitName Value="fpsfunc"/>
<EditorIndex Value="3"/>
<EditorIndex Value="4"/>
<TopLine Value="113"/>
<CursorPos X="10" Y="132"/>
<UsageCount Value="13"/>
<UsageCount Value="18"/>
<Loaded Value="True"/>
</Unit2>
<Unit3>
<Filename Value="..\..\fpsutils.pas"/>
<UnitName Value="fpsutils"/>
<EditorIndex Value="2"/>
<EditorIndex Value="3"/>
<TopLine Value="876"/>
<CursorPos Y="894"/>
<UsageCount Value="13"/>
<UsageCount Value="18"/>
<Loaded Value="True"/>
</Unit3>
<Unit4>
<Filename Value="..\..\fpolebasic.pas"/>
<UnitName Value="fpolebasic"/>
<EditorIndex Value="1"/>
<EditorIndex Value="2"/>
<TopLine Value="43"/>
<CursorPos X="29" Y="53"/>
<UsageCount Value="13"/>
<UsageCount Value="18"/>
<Loaded Value="True"/>
</Unit4>
<Unit5>
<Filename Value="..\..\xlsbiff8.pas"/>
<UnitName Value="xlsbiff8"/>
<EditorIndex Value="5"/>
<EditorIndex Value="6"/>
<TopLine Value="63"/>
<CursorPos X="20" Y="83"/>
<UsageCount Value="12"/>
<UsageCount Value="17"/>
<Loaded Value="True"/>
</Unit5>
<Unit6>
<Filename Value="..\..\xlscommon.pas"/>
<UnitName Value="xlscommon"/>
<EditorIndex Value="6"/>
<EditorIndex Value="7"/>
<TopLine Value="1084"/>
<CursorPos Y="1103"/>
<UsageCount Value="12"/>
<UsageCount Value="17"/>
<Loaded Value="True"/>
</Unit6>
<Unit7>
<Filename Value="d:\lazarus-svn\fpc\2.6.4\source\rtl\objpas\math.pp"/>
<UnitName Value="math"/>
<EditorIndex Value="1"/>
<TopLine Value="294"/>
<CursorPos X="10" Y="313"/>
<UsageCount Value="13"/>
<Loaded Value="True"/>
</Unit7>
</Units>
<JumpHistory Count="30" HistoryIndex="29">
<Position1>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Caret Line="1521" TopLine="1501"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="66" Column="12" TopLine="53"/>
</Position1>
<Position2>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Caret Line="1522" TopLine="1501"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="78" Column="28" TopLine="59"/>
</Position2>
<Position3>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Caret Line="1523" TopLine="1501"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="70" TopLine="59"/>
</Position3>
<Position4>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Caret Line="1548" TopLine="1544"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="71" TopLine="59"/>
</Position4>
<Position5>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Caret Line="1562" TopLine="1544"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="73" TopLine="59"/>
</Position5>
<Position6>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Caret Line="1452" TopLine="1432"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="74" TopLine="59"/>
</Position6>
<Position7>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Caret Line="1562" TopLine="1543"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="77" TopLine="59"/>
</Position7>
<Position8>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Caret Line="1567" TopLine="1543"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="78" TopLine="59"/>
</Position8>
<Position9>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Caret Line="1573" TopLine="1543"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="96" Column="46" TopLine="59"/>
</Position9>
<Position10>
<Filename Value="test_formula_func.pas"/>
<Caret Line="125" TopLine="105"/>
<Caret Line="362" TopLine="262"/>
</Position10>
<Position11>
<Filename Value="test_formula_func.pas"/>
<Caret Line="126" TopLine="105"/>
<Caret Line="70" TopLine="51"/>
</Position11>
<Position12>
<Filename Value="test_formula_func.pas"/>
<Caret Line="128" TopLine="105"/>
<Caret Line="71" TopLine="51"/>
</Position12>
<Position13>
<Filename Value="test_formula_func.pas"/>
<Caret Line="133" TopLine="105"/>
<Caret Line="73" TopLine="51"/>
</Position13>
<Position14>
<Filename Value="test_formula_func.pas"/>
<Caret Line="130" TopLine="105"/>
<Caret Line="74" TopLine="51"/>
</Position14>
<Position15>
<Filename Value="test_formula_func.pas"/>
<Caret Line="133" TopLine="105"/>
<Caret Line="77" TopLine="51"/>
</Position15>
<Position16>
<Filename Value="test_formula_func.pas"/>
<Caret Line="131" TopLine="105"/>
<Caret Line="78" TopLine="51"/>
</Position16>
<Position17>
<Filename Value="test_formula_func.pas"/>
<Caret Line="133" TopLine="105"/>
<Caret Line="69" Column="42" TopLine="51"/>
</Position17>
<Position18>
<Filename Value="test_formula_func.pas"/>
<Caret Line="132" TopLine="105"/>
<Caret Line="82" TopLine="63"/>
</Position18>
<Position19>
<Filename Value="test_formula_func.pas"/>
<Caret Line="133" TopLine="105"/>
<Caret Line="83" Column="29" TopLine="63"/>
</Position19>
<Position20>
<Filename Value="test_formula_func.pas"/>
<Caret Line="129" TopLine="105"/>
<Caret Line="86" Column="22" TopLine="63"/>
</Position20>
<Position21>
<Filename Value="test_formula_func.pas"/>
<Caret Line="133" TopLine="105"/>
<Caret Line="96" Column="103" TopLine="78"/>
</Position21>
<Position22>
<Filename Value="test_formula_func.pas"/>
<Caret Line="90" TopLine="70"/>
<Caret Line="99" TopLine="68"/>
</Position22>
<Position23>
<Filename Value="test_formula_func.pas"/>
<Caret Line="75" Column="25" TopLine="71"/>
<Caret Line="97" Column="68" TopLine="69"/>
</Position23>
<Position24>
<Filename Value="test_formula_func.pas"/>
<Caret Line="127" Column="34" TopLine="102"/>
<Caret Line="98" Column="13" TopLine="64"/>
</Position24>
<Position25>
<Filename Value="test_formula_func.pas"/>
<Caret Line="280" TopLine="261"/>
<Caret Line="97" Column="3" TopLine="68"/>
</Position25>
<Position26>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Caret Line="1543" Column="32" TopLine="1543"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="96" Column="3" TopLine="68"/>
</Position26>
<Position27>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="98" Column="13" TopLine="71"/>
</Position27>
<Position28>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Caret Line="67" Column="34" TopLine="35"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="97" Column="9" TopLine="71"/>
</Position28>
<Position29>
<Filename Value="..\..\fpspreadsheet.pas"/>
<Caret Line="1121" Column="10" TopLine="1089"/>
<Filename Value="test_formula_func.pas"/>
<Caret Line="92" Column="11" TopLine="71"/>
</Position29>
<Position30>
<Filename Value="test_formula_func.pas"/>
<Caret Line="269" Column="29" TopLine="261"/>
<Caret Line="367" Column="28" TopLine="346"/>
</Position30>
</JumpHistory>
</ProjectSession>

View File

@ -3,9 +3,10 @@
calculation procedure.
The example will show implementation of the some financial formulas:
- FV(...) (future value)
- PV(...) (present value)
- PMT(...) (payment)
- FV() (future value)
- PV() (present value)
- PMT() (payment)
- NPER() (number of payment periods)
The demo writes an xls file which uses these formulas and then displays
the result in a console window. (Open the generated file in Excel or
@ -27,25 +28,31 @@ uses
{------------------------------------------------------------------------------}
{ Basic implmentation of the three financial funtions }
{ Basic implmentation of the four financial funtions }
{------------------------------------------------------------------------------}
const
paymentAtEnd = 0;
paymentAtBegin = 1;
{ Calculates the future value of an investment based on an interest rate and
{ Calculates the future value (FV) of an investment based on an interest rate and
a constant payment schedule:
- "interest_rate" is the interest rate for the investment (as decimal, not percent)
- "number_periods" is the number of payment periods, i.e. number of payments
- "interest_rate" (r) is the interest rate for the investment (as decimal, not percent)
- "number_periods" (n) is the number of payment periods, i.e. number of payments
for the annuity.
- "payment" is the amount of the payment made each period
- "payment" (PMT) is the amount of the payment made each period
- "pv" is the present value of the payments.
- "payment_type" indicates when the payments are due (see paymentAtXXX constants)
see: http://en.wikipedia.org/wiki/Time_value_of_money
In Excel's implementation the payments and the FV add up to 0:
FV + PV q^n + PMT (q^n - 1) / (q - 1) = 0
see: http://en.wikipedia.org/wiki/Time_value_of_money
or https://wiki.openoffice.org/wiki/Documentation/How_Tos/Calc:_Derivation_of_Financial_Formulas#PV.2C_FV.2C_PMT.2C_NPER.2C_RATE
As in Excel's implementation the cash flow is a signed number:
- Positive cash flow means: "I get money"
- Negative cash flow means: "I pay money"
With these conventions, the contributions (FV, PV, Payments) add up to 0:
FV + PV q^n + PMT (q^n - 1) / (q - 1) = 0 ( q = 1 + r )
}
function FV(interest_rate: Double; number_periods: Integer; payment, pv: Double;
payment_type: integer): Double;
@ -61,6 +68,30 @@ begin
Result := -(pv * qn + payment*factor);
end;
{ Calculates the number of periods for an investment based on an interest rate
and a constant payment schedule.
Solve above formula for qn and then take the log to get n.
}
function NPER(interest_rate, payment, pv, fv: Double;
payment_type:Integer): double;
var
q, x1, x2, T: Double;
begin
if interest_rate = 0 then
Result := (pv + fv) / payment
else
q := 1.0 + interest_rate;
if payment_type = paymentAtBegin then
payment := payment * q;
x2 := pv * interest_rate + payment;
if x2 = 0 then
Result := Infinity
else begin
x1 := -fv * interest_rate + payment;
Result := ln(x1/x2) / ln(q);
end;
end;
{ Calculates the regular payments for a loan based on an interest rate and a
constant payment schedule
Arguments as shown for FV(), in addition:
@ -152,6 +183,19 @@ begin
));
end;
function fpsNPER(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
var
data: TsArgNumberArray;
begin
if Args.PopNumberValues(NumArgs, false, data, Result) then
Result := CreateNumberArg(NPER(
data[0], // interest rate
data[1], // payment
data[2], // present value
data[3], // future value
round(data[4]) // payment type
));
end;
{------------------------------------------------------------------------------}
{ Write xls file comparing our own calculations with Excel result }
@ -167,7 +211,7 @@ const
var
workbook: TsWorkbook;
worksheet: TsWorksheet;
fval, pval, pmtval: Double;
fval, pval, pmtval, nperval: Double;
begin
{ We have to register our financial function in fpspreadsheet. Otherwise an
@ -176,6 +220,7 @@ begin
RegisterFormulaFunc(fekFV, @fpsFV);
RegisterFormulaFunc(fekPMT, @fpsPMT);
RegisterFormulaFunc(fekPV, @fpsPV);
RegisterFormulaFunc(fekNPER, @fpsNPER);
workbook := TsWorkbook.Create;
try
@ -200,7 +245,7 @@ begin
// future value calculation
fval := FV(INTEREST_RATE, NUMBER_PAYMENTS, PAYMENT, PRESENT_VALUE, PAYMENT_WHEN);
worksheet.WriteUTF8Text(6, 0, 'Future value');
worksheet.WriteUTF8Text(6, 0, 'Calculation of the future value');
worksheet.WriteFontStyle(6, 0, [fssBold]);
worksheet.WriteUTF8Text(7, 0, 'Our calculation');
worksheet.WriteCurrency(7, 1, fval, nfCurrency, 2, '$');
@ -228,7 +273,7 @@ begin
// present value calculation
pval := PV(INTEREST_RATE, NUMBER_PAYMENTS, PAYMENT, fval, PAYMENT_WHEN);
worksheet.WriteUTF8Text(11, 0, 'Present value');
worksheet.WriteUTF8Text(11, 0, 'Calculation of the present value');
worksheet.WriteFontStyle(11, 0, [fssBold]);
worksheet.WriteUTF8Text(12, 0, 'Our calculation');
worksheet.WriteCurrency(12, 1, pval, nfCurrency, 2, '$');
@ -256,7 +301,7 @@ begin
// payments calculation
pmtval := PMT(INTEREST_RATE, NUMBER_PAYMENTS, PRESENT_VALUE, fval, PAYMENT_WHEN);
worksheet.WriteUTF8Text(16, 0, 'Payment');
worksheet.WriteUTF8Text(16, 0, 'Calculation of the payment');
worksheet.WriteFontStyle(16, 0, [fssBold]);
worksheet.WriteUTF8Text(17, 0, 'Our calculation');
worksheet.WriteCurrency(17, 1, pmtval, nfCurrency, 2, '$');
@ -282,6 +327,34 @@ begin
RPNFunc(fekPMT, 5, // Call Excel's PMT formula
nil))))))));
// number of periods calculation
nperval := NPER(INTEREST_RATE, PAYMENT, PRESENT_VALUE, fval, PAYMENT_WHEN);
worksheet.WriteUTF8Text(21, 0, 'Calculation of the number of payment periods');
worksheet.WriteFontStyle(21, 0, [fssBold]);
worksheet.WriteUTF8Text(22, 0, 'Our calculation');
worksheet.WriteNumber(22, 1, nperval, nfFixed, 2);
worksheet.WriteUTF8Text(23, 0, 'Excel''s calculation using constants');
worksheet.WriteNumberFormat(23, 1, nfFixed, 2);
worksheet.WriteRPNFormula(23, 1, CreateRPNFormula(
RPNNumber(INTEREST_RATE,
RPNNumber(PAYMENT,
RPNNumber(PRESENT_VALUE,
RPNNumber(fval,
RPNNumber(PAYMENT_WHEN,
RPNFunc(fekNPER, 5,
nil))))))));
Worksheet.WriteUTF8Text(24, 0, 'Excel''s calculation using cell values');
worksheet.WriteNumberFormat(24, 1, nfFixed, 2);
worksheet.WriteRPNFormula(24, 1, CreateRPNFormula(
RPNCellValue('B1', // interest rate
RPNCellValue('B3', // payment
RPNCellValue('B4', // present value
RPNCellValue('B10', // future value
RPNCellValue('B5', // payment at end or at start
RPNFunc(fekNPER, 5, // Call Excel's PMT formula
nil))))))));
workbook.WriteToFile(AFileName, sfExcel8, true);
finally
@ -308,7 +381,7 @@ begin
// Write all cells with contents to the console
WriteLn('');
WriteLn('Contents of the first worksheet of the file:');
WriteLn('Contents of file "', AFileName, '"');
WriteLn('');
for r := 0 to worksheet.GetLastRowIndex do begin