2014-07-05 21:48:12 +00:00
|
|
|
{ This demo show how user-provided functions can be used for calculation of
|
2014-07-02 22:03:03 +00:00
|
|
|
rpn formulas that are built-in to fpspreadsheet, but don't have an own
|
2014-07-05 21:48:12 +00:00
|
|
|
calculation procedure.
|
|
|
|
|
|
|
|
The example will show implementation of the some financial formulas:
|
|
|
|
- FV(...) (future value)
|
|
|
|
- PV(...) (present value)
|
|
|
|
- PMT(...) (payment)
|
|
|
|
|
|
|
|
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
|
|
|
|
Open/LibreOffice and compare).
|
|
|
|
}
|
2014-07-02 22:03:03 +00:00
|
|
|
|
|
|
|
program test_formula_func;
|
|
|
|
|
2014-07-05 21:48:12 +00:00
|
|
|
//{$mode objfpc}{$H+}
|
|
|
|
{$mode delphi}{$H+}
|
2014-07-02 22:03:03 +00:00
|
|
|
|
|
|
|
uses
|
2014-07-05 21:48:12 +00:00
|
|
|
{$IFDEF UNIX}{$IFDEF UseCThreads}
|
2014-07-02 22:03:03 +00:00
|
|
|
cthreads,
|
|
|
|
{$ENDIF}{$ENDIF}
|
2014-07-05 21:48:12 +00:00
|
|
|
Classes, SysUtils, laz_fpspreadsheet
|
2014-07-02 22:03:03 +00:00
|
|
|
{ you can add units after this },
|
2014-07-05 21:48:12 +00:00
|
|
|
math, fpspreadsheet, xlsbiff8, fpsfunc;
|
|
|
|
|
|
|
|
|
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
{ Basic implmentation of the three financial funtions }
|
|
|
|
{------------------------------------------------------------------------------}
|
2014-07-02 22:03:03 +00:00
|
|
|
|
|
|
|
const
|
|
|
|
paymentAtEnd = 0;
|
|
|
|
paymentAtBegin = 1;
|
|
|
|
|
|
|
|
{ Calculates the future value 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
|
|
|
|
for the annuity.
|
|
|
|
- "payment" is the amount of the payment made each period
|
2014-07-05 21:48:12 +00:00
|
|
|
- "pv" is the present value of the payments.
|
2014-07-02 22:03:03 +00:00
|
|
|
- "payment_type" indicates when the payments are due (see paymentAtXXX constants)
|
2014-07-05 21:48:12 +00:00
|
|
|
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
|
2014-07-02 22:03:03 +00:00
|
|
|
}
|
2014-07-05 21:48:12 +00:00
|
|
|
function FV(interest_rate: Double; number_periods: Integer; payment, pv: Double;
|
2014-07-02 22:03:03 +00:00
|
|
|
payment_type: integer): Double;
|
|
|
|
var
|
2014-07-05 21:48:12 +00:00
|
|
|
q, qn, factor: Double;
|
|
|
|
begin
|
|
|
|
q := 1.0 + interest_rate;
|
|
|
|
qn := power(q, number_periods);
|
|
|
|
factor := (qn - 1) / (q - 1);
|
|
|
|
if payment_type = paymentAtBegin then
|
|
|
|
factor := factor * q;
|
|
|
|
|
|
|
|
Result := -(pv * qn + payment*factor);
|
|
|
|
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:
|
|
|
|
- "fv" is the future value of the payments.
|
|
|
|
see: http://en.wikipedia.org/wiki/Time_value_of_money
|
|
|
|
}
|
|
|
|
function PMT(interest_rate: Double; number_periods: Integer; pv, fv: Double;
|
|
|
|
payment_type: Integer): Double;
|
|
|
|
var
|
|
|
|
q, qn, factor: Double;
|
2014-07-02 22:03:03 +00:00
|
|
|
begin
|
|
|
|
q := 1.0 + interest_rate;
|
2014-07-05 21:48:12 +00:00
|
|
|
qn := power(q, number_periods);
|
|
|
|
factor := (qn - 1) / (q - 1);
|
|
|
|
if payment_type = paymentAtBegin then
|
|
|
|
factor := factor * q;
|
2014-07-02 22:03:03 +00:00
|
|
|
|
2014-07-05 21:48:12 +00:00
|
|
|
Result := -(fv + pv * qn) / factor;
|
|
|
|
end;
|
2014-07-02 22:03:03 +00:00
|
|
|
|
2014-07-05 21:48:12 +00:00
|
|
|
{ Calculates the present value of an investment based on an interest rate and
|
|
|
|
a constant payment schedule.
|
|
|
|
Arguments as shown for FV(), in addition:
|
|
|
|
- "fv" is the future value of the payments.
|
|
|
|
see: http://en.wikipedia.org/wiki/Time_value_of_money
|
|
|
|
}
|
|
|
|
function PV(interest_rate: Double; number_periods: Integer; payment, fv: Double;
|
|
|
|
payment_type: Integer): Double;
|
|
|
|
var
|
|
|
|
q, qn, factor: Double;
|
|
|
|
begin
|
|
|
|
q := 1.0 + interest_rate;
|
|
|
|
qn := power(q, number_periods);
|
|
|
|
factor := (qn - 1) / (q - 1);
|
2014-07-02 22:03:03 +00:00
|
|
|
if payment_type = paymentAtBegin then
|
2014-07-05 21:48:12 +00:00
|
|
|
factor := factor * q;
|
|
|
|
|
|
|
|
Result := -(fv + payment*factor) / qn;
|
2014-07-02 22:03:03 +00:00
|
|
|
end;
|
|
|
|
|
2014-07-05 21:48:12 +00:00
|
|
|
|
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
{ Adaption for usage by fpspreadsheet }
|
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
|
2014-07-02 22:03:03 +00:00
|
|
|
function fpsFV(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
|
|
|
|
var
|
2014-07-03 15:38:15 +00:00
|
|
|
data: TsArgNumberArray;
|
2014-07-02 22:03:03 +00:00
|
|
|
begin
|
2014-07-03 15:38:15 +00:00
|
|
|
// Pop the argument off the stack. This can be done by means of PopNumberValues
|
|
|
|
// which brings the values back into the right order and reports an error
|
|
|
|
// in case of non-numerical values.
|
|
|
|
if Args.PopNumberValues(NumArgs, false, data, Result) then
|
|
|
|
// Call our FV function with the NumberValues of the arguments.
|
2014-07-04 21:17:15 +00:00
|
|
|
Result := CreateNumberArg(FV(
|
2014-07-05 21:48:12 +00:00
|
|
|
data[0], // interest rate
|
|
|
|
round(data[1]), // number of payments
|
|
|
|
data[2], // payment
|
|
|
|
data[3], // present value
|
|
|
|
round(data[4]) // payment type
|
|
|
|
));
|
|
|
|
end;
|
|
|
|
|
|
|
|
function fpsPMT(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
|
|
|
|
var
|
|
|
|
data: TsArgNumberArray;
|
|
|
|
begin
|
|
|
|
if Args.PopNumberValues(NumArgs, false, data, Result) then
|
|
|
|
Result := CreateNumberArg(PMT(
|
|
|
|
data[0], // interest rate
|
|
|
|
round(data[1]), // number of payments
|
|
|
|
data[2], // present value
|
|
|
|
data[3], // future value
|
|
|
|
round(data[4]) // payment type
|
|
|
|
));
|
|
|
|
end;
|
|
|
|
|
|
|
|
function fpsPV(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
|
|
|
|
var
|
|
|
|
data: TsArgNumberArray;
|
|
|
|
begin
|
|
|
|
if Args.PopNumberValues(NumArgs, false, data, Result) then
|
|
|
|
Result := CreateNumberArg(PV(
|
|
|
|
data[0], // interest rate
|
|
|
|
round(data[1]), // number of payments
|
|
|
|
data[2], // payment
|
|
|
|
data[3], // future value
|
|
|
|
round(data[4]) // payment type
|
2014-07-03 15:38:15 +00:00
|
|
|
));
|
2014-07-02 22:03:03 +00:00
|
|
|
end;
|
|
|
|
|
2014-07-05 21:48:12 +00:00
|
|
|
|
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
{ Write xls file comparing our own calculations with Excel result }
|
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
procedure WriteFile(AFileName: String);
|
2014-07-02 22:03:03 +00:00
|
|
|
const
|
|
|
|
INTEREST_RATE = 0.03;
|
|
|
|
NUMBER_PAYMENTS = 10;
|
|
|
|
PAYMENT = 1000;
|
|
|
|
PRESENT_VALUE = 10000;
|
|
|
|
PAYMENT_WHEN = paymentAtEnd;
|
|
|
|
|
|
|
|
var
|
|
|
|
workbook: TsWorkbook;
|
|
|
|
worksheet: TsWorksheet;
|
2014-07-05 21:48:12 +00:00
|
|
|
fval, pval, pmtval: Double;
|
2014-07-02 22:03:03 +00:00
|
|
|
|
|
|
|
begin
|
2014-07-05 21:48:12 +00:00
|
|
|
{ We have to register our financial function in fpspreadsheet. Otherwise an
|
|
|
|
error code would be displayed in the reading part of this demo in these
|
|
|
|
formula cells. }
|
2014-07-02 22:03:03 +00:00
|
|
|
RegisterFormulaFunc(fekFV, @fpsFV);
|
2014-07-05 21:48:12 +00:00
|
|
|
RegisterFormulaFunc(fekPMT, @fpsPMT);
|
|
|
|
RegisterFormulaFunc(fekPV, @fpsPV);
|
2014-07-02 22:03:03 +00:00
|
|
|
|
|
|
|
workbook := TsWorkbook.Create;
|
|
|
|
try
|
|
|
|
worksheet := workbook.AddWorksheet('Financial');
|
|
|
|
worksheet.Options := worksheet.Options + [soCalcBeforeSaving];
|
2014-07-05 21:48:12 +00:00
|
|
|
worksheet.WriteColWidth(0, 40);
|
2014-07-02 22:03:03 +00:00
|
|
|
|
|
|
|
worksheet.WriteUTF8Text(0, 0, 'Interest rate');
|
2014-07-05 21:48:12 +00:00
|
|
|
worksheet.WriteNumber(0, 1, INTEREST_RATE, nfPercentage, 1); // B1
|
2014-07-02 22:03:03 +00:00
|
|
|
|
|
|
|
worksheet.WriteUTF8Text(1, 0, 'Number of payments');
|
2014-07-05 21:48:12 +00:00
|
|
|
worksheet.WriteNumber(1, 1, NUMBER_PAYMENTS); // B2
|
2014-07-02 22:03:03 +00:00
|
|
|
|
|
|
|
worksheet.WriteUTF8Text(2, 0, 'Payment');
|
2014-07-05 21:48:12 +00:00
|
|
|
worksheet.WriteCurrency(2, 1, PAYMENT, nfCurrency, 2, '$'); // B3
|
2014-07-02 22:03:03 +00:00
|
|
|
|
|
|
|
worksheet.WriteUTF8Text(3, 0, 'Present value');
|
2014-07-05 21:48:12 +00:00
|
|
|
worksheet.WriteCurrency(3, 1, PRESENT_VALUE, nfCurrency, 2, '$'); // B4
|
2014-07-02 22:03:03 +00:00
|
|
|
|
2014-07-05 21:48:12 +00:00
|
|
|
worksheet.WriteUTF8Text(4, 0, 'Payment at end (0) or at begin (1)');
|
|
|
|
worksheet.WriteNumber(4, 1, PAYMENT_WHEN); // B5
|
2014-07-02 22:03:03 +00:00
|
|
|
|
2014-07-05 21:48:12 +00:00
|
|
|
// future value calculation
|
|
|
|
fval := FV(INTEREST_RATE, NUMBER_PAYMENTS, PAYMENT, PRESENT_VALUE, PAYMENT_WHEN);
|
2014-07-02 22:03:03 +00:00
|
|
|
worksheet.WriteUTF8Text(6, 0, 'Future value');
|
|
|
|
worksheet.WriteFontStyle(6, 0, [fssBold]);
|
|
|
|
worksheet.WriteUTF8Text(7, 0, 'Our calculation');
|
2014-07-05 21:48:12 +00:00
|
|
|
worksheet.WriteCurrency(7, 1, fval, nfCurrency, 2, '$');
|
2014-07-02 22:03:03 +00:00
|
|
|
|
2014-07-05 21:48:12 +00:00
|
|
|
worksheet.WriteUTF8Text(8, 0, 'Excel''s calculation using constants');
|
2014-07-02 22:03:03 +00:00
|
|
|
worksheet.WriteNumberFormat(8, 1, nfCurrency, 2, '$');
|
2014-07-05 21:48:12 +00:00
|
|
|
worksheet.WriteRPNFormula(8, 1, CreateRPNFormula( // B9
|
2014-07-02 22:03:03 +00:00
|
|
|
RPNNumber(INTEREST_RATE,
|
|
|
|
RPNNumber(NUMBER_PAYMENTS,
|
2014-07-05 21:48:12 +00:00
|
|
|
RPNNumber(PAYMENT,
|
|
|
|
RPNNumber(PRESENT_VALUE,
|
2014-07-02 22:03:03 +00:00
|
|
|
RPNNumber(PAYMENT_WHEN,
|
|
|
|
RPNFunc(fekFV, 5,
|
|
|
|
nil))))))));
|
2014-07-05 21:48:12 +00:00
|
|
|
worksheet.WriteUTF8Text(9, 0, 'Excel''s calculation using cell values');
|
|
|
|
worksheet.WriteNumberFormat(9, 1, nfCurrency, 2, '$');
|
|
|
|
worksheet.WriteRPNFormula(9, 1, CreateRPNFormula( // B9
|
|
|
|
RPNCellValue('B1', // interest rate
|
|
|
|
RPNCellValue('B2', // number of periods
|
|
|
|
RPNCellValue('B3', // payment
|
|
|
|
RPNCellValue('B4', // present value
|
|
|
|
RPNCellValue('B5', // payment at end or at start
|
|
|
|
RPNFunc(fekFV, 5, // Call Excel's FV formula
|
|
|
|
nil))))))));
|
|
|
|
|
|
|
|
// present value calculation
|
|
|
|
pval := PV(INTEREST_RATE, NUMBER_PAYMENTS, PAYMENT, fval, PAYMENT_WHEN);
|
|
|
|
worksheet.WriteUTF8Text(11, 0, 'Present value');
|
|
|
|
worksheet.WriteFontStyle(11, 0, [fssBold]);
|
|
|
|
worksheet.WriteUTF8Text(12, 0, 'Our calculation');
|
|
|
|
worksheet.WriteCurrency(12, 1, pval, nfCurrency, 2, '$');
|
|
|
|
|
|
|
|
worksheet.WriteUTF8Text(13, 0, 'Excel''s calculation using constants');
|
|
|
|
worksheet.WriteNumberFormat(13, 1, nfCurrency, 2, '$');
|
|
|
|
worksheet.WriteRPNFormula(13, 1, CreateRPNFormula(
|
|
|
|
RPNNumber(INTEREST_RATE,
|
|
|
|
RPNNumber(NUMBER_PAYMENTS,
|
|
|
|
RPNNumber(PAYMENT,
|
|
|
|
RPNNumber(fval,
|
|
|
|
RPNNumber(PAYMENT_WHEN,
|
|
|
|
RPNFunc(fekPV, 5,
|
|
|
|
nil))))))));
|
|
|
|
Worksheet.WriteUTF8Text(14, 0, 'Excel''s calculation using cell values');
|
|
|
|
worksheet.WriteNumberFormat(14, 1, nfCurrency, 2, '$');
|
|
|
|
worksheet.WriteRPNFormula(14, 1, CreateRPNFormula(
|
|
|
|
RPNCellValue('B1', // interest rate
|
|
|
|
RPNCellValue('B2', // number of periods
|
|
|
|
RPNCellValue('B3', // payment
|
|
|
|
RPNCellValue('B10', // future value
|
|
|
|
RPNCellValue('B5', // payment at end or at start
|
|
|
|
RPNFunc(fekPV, 5, // Call Excel's PV formula
|
|
|
|
nil))))))));
|
|
|
|
|
|
|
|
// payments calculation
|
|
|
|
pmtval := PMT(INTEREST_RATE, NUMBER_PAYMENTS, PRESENT_VALUE, fval, PAYMENT_WHEN);
|
|
|
|
worksheet.WriteUTF8Text(16, 0, 'Payment');
|
|
|
|
worksheet.WriteFontStyle(16, 0, [fssBold]);
|
|
|
|
worksheet.WriteUTF8Text(17, 0, 'Our calculation');
|
|
|
|
worksheet.WriteCurrency(17, 1, pmtval, nfCurrency, 2, '$');
|
|
|
|
|
|
|
|
worksheet.WriteUTF8Text(18, 0, 'Excel''s calculation using constants');
|
|
|
|
worksheet.WriteNumberFormat(18, 1, nfCurrency, 2, '$');
|
|
|
|
worksheet.WriteRPNFormula(18, 1, CreateRPNFormula(
|
|
|
|
RPNNumber(INTEREST_RATE,
|
|
|
|
RPNNumber(NUMBER_PAYMENTS,
|
|
|
|
RPNNumber(PRESENT_VALUE,
|
|
|
|
RPNNumber(fval,
|
|
|
|
RPNNumber(PAYMENT_WHEN,
|
|
|
|
RPNFunc(fekPMT, 5,
|
|
|
|
nil))))))));
|
|
|
|
Worksheet.WriteUTF8Text(19, 0, 'Excel''s calculation using cell values');
|
|
|
|
worksheet.WriteNumberFormat(19, 1, nfCurrency, 2, '$');
|
|
|
|
worksheet.WriteRPNFormula(19, 1, CreateRPNFormula(
|
|
|
|
RPNCellValue('B1', // interest rate
|
|
|
|
RPNCellValue('B2', // number of periods
|
|
|
|
RPNCellValue('B4', // present value
|
|
|
|
RPNCellValue('B10', // future value
|
|
|
|
RPNCellValue('B5', // payment at end or at start
|
|
|
|
RPNFunc(fekPMT, 5, // Call Excel's PMT formula
|
|
|
|
nil))))))));
|
|
|
|
|
|
|
|
workbook.WriteToFile(AFileName, sfExcel8, true);
|
|
|
|
|
|
|
|
finally
|
|
|
|
workbook.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
{ Read xls file to display Excel's results }
|
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
procedure ReadFile(AFileName: String);
|
|
|
|
var
|
|
|
|
workbook: TsWorkbook;
|
|
|
|
worksheet: TsWorksheet;
|
|
|
|
r: Cardinal;
|
|
|
|
s1, s2: String;
|
|
|
|
begin
|
|
|
|
workbook := TsWorkbook.Create;
|
|
|
|
try
|
|
|
|
workbook.ReadFormulas := true;
|
|
|
|
workbook.ReadFromFile(AFilename, sfExcel8);
|
|
|
|
|
|
|
|
worksheet := workbook.GetFirstWorksheet;
|
2014-07-02 22:03:03 +00:00
|
|
|
|
2014-07-05 21:48:12 +00:00
|
|
|
// Write all cells with contents to the console
|
|
|
|
WriteLn('');
|
|
|
|
WriteLn('Contents of the first worksheet of the file:');
|
|
|
|
WriteLn('');
|
2014-07-03 15:38:15 +00:00
|
|
|
|
2014-07-05 21:48:12 +00:00
|
|
|
for r := 0 to worksheet.GetLastRowIndex do begin
|
|
|
|
s1 := UTF8ToAnsi(worksheet.ReadAsUTF8Text(r, 0));
|
|
|
|
s2 := UTF8ToAnsi(worksheet.ReadAsUTF8Text(r, 1));
|
|
|
|
if s1 = '' then
|
|
|
|
WriteLn
|
|
|
|
else
|
|
|
|
WriteLn(s1+': ':50, s2);
|
|
|
|
end;
|
|
|
|
|
|
|
|
WriteLn;
|
|
|
|
WriteLn('Press [ENTER] to close...');
|
|
|
|
ReadLn;
|
2014-07-02 22:03:03 +00:00
|
|
|
finally
|
|
|
|
workbook.Free;
|
|
|
|
end;
|
2014-07-05 21:48:12 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
begin
|
|
|
|
WriteFile('test_fv.xls');
|
|
|
|
ReadFile('test_fv.xls');
|
2014-07-02 22:03:03 +00:00
|
|
|
end.
|
|
|
|
|
2014-07-05 21:48:12 +00:00
|
|
|
|