
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3505 8e941d3f-bd1b-0410-a28a-d453659cc2b4
359 lines
13 KiB
ObjectPascal
359 lines
13 KiB
ObjectPascal
{ This demo shows how user-provided functions can be used for calculation of
|
|
RPN formulas that are built-in to fpspreadsheet, but don't have their own
|
|
calculation procedure.
|
|
|
|
The example will show implementation of some financial formulas:
|
|
- 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
|
|
Open/LibreOffice and compare).
|
|
}
|
|
|
|
program demo_formula_func;
|
|
|
|
{$mode delphi}{$H+}
|
|
|
|
uses
|
|
{$IFDEF UNIX}
|
|
{$IFDEF UseCThreads}
|
|
cthreads,
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
Classes, SysUtils,
|
|
math, fpspreadsheet, xlsbiff8, fpsfunc, financemath;
|
|
|
|
|
|
{------------------------------------------------------------------------------}
|
|
{ Adaption of financial functions to usage by fpspreadsheet }
|
|
{ The functions are implemented in the unit "financemath.pas". }
|
|
{------------------------------------------------------------------------------}
|
|
|
|
function fpsFV(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
|
|
var
|
|
data: TsArgNumberArray;
|
|
begin
|
|
// Pop the argument from the stack. This can be done by means of PopNumberValues
|
|
// which brings the values back in the right order and reports an error
|
|
// in case of non-numerical values.
|
|
if Args.PopNumberValues(NumArgs, false, data, Result) then
|
|
// Call the FutureValue function with the NumberValues of the arguments.
|
|
Result := CreateNumberArg(FutureValue(
|
|
data[0], // interest rate
|
|
round(data[1]), // number of payments
|
|
data[2], // payment
|
|
data[3], // present value
|
|
TPaymentTime(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(Payment(
|
|
data[0], // interest rate
|
|
round(data[1]), // number of payments
|
|
data[2], // present value
|
|
data[3], // future value
|
|
TPaymentTime(round(data[4])) // payment type
|
|
));
|
|
end;
|
|
|
|
function fpsPV(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
|
|
// Present value
|
|
var
|
|
data: TsArgNumberArray;
|
|
begin
|
|
if Args.PopNumberValues(NumArgs, false, data, Result) then
|
|
Result := CreateNumberArg(PresentValue(
|
|
data[0], // interest rate
|
|
round(data[1]), // number of payments
|
|
data[2], // payment
|
|
data[3], // future value
|
|
TPaymentTime(round(data[4])) // payment type
|
|
));
|
|
end;
|
|
|
|
function fpsNPER(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
|
|
var
|
|
data: TsArgNumberArray;
|
|
begin
|
|
if Args.PopNumberValues(NumArgs, false, data, Result) then
|
|
Result := CreateNumberArg(NumberOfPeriods(
|
|
data[0], // interest rate
|
|
data[1], // payment
|
|
data[2], // present value
|
|
data[3], // future value
|
|
TPaymentTime(round(data[4])) // payment type
|
|
));
|
|
end;
|
|
|
|
function fpsRATE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
|
|
var
|
|
data: TsArgNumberArray;
|
|
begin
|
|
if Args.PopNumberValues(NumArgs, false, data, Result) then
|
|
Result := CreateNumberArg(InterestRate(
|
|
round(data[0]), // number of payment periods
|
|
data[1], // payment
|
|
data[2], // present value
|
|
data[3], // future value
|
|
TPaymentTime(round(data[4])) // payment type
|
|
));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------}
|
|
{ Write xls file comparing our own calculations with Excel result }
|
|
{------------------------------------------------------------------------------}
|
|
procedure WriteFile(AFileName: String);
|
|
const
|
|
INTEREST_RATE = 0.03; // interest rate per period
|
|
NUMBER_PAYMENTS = 10; // number of payment periods
|
|
REG_PAYMENT = 1000; // regular payment per period
|
|
PRESENT_VALUE = 10000; // present value of investment
|
|
PAYMENT_WHEN: TPaymentTime = ptEndOfPeriod; // when is the payment made
|
|
|
|
var
|
|
workbook: TsWorkbook;
|
|
worksheet: TsWorksheet;
|
|
fval, pval, pmtval, nperval, rateval: Double;
|
|
begin
|
|
{ We have to register our financial functions in fpspreadsheet. Otherwise an
|
|
error code would be displayed in the reading part of this demo for these
|
|
formula cells. }
|
|
RegisterFormulaFunc(fekFV, @fpsFV);
|
|
RegisterFormulaFunc(fekPMT, @fpsPMT);
|
|
RegisterFormulaFunc(fekPV, @fpsPV);
|
|
RegisterFormulaFunc(fekNPER, @fpsNPER);
|
|
RegisterFormulaFunc(fekRATE, @fpsRATE);
|
|
|
|
workbook := TsWorkbook.Create;
|
|
try
|
|
workbook.Options := workbook.Options + [boCalcBeforeSaving];
|
|
|
|
worksheet := workbook.AddWorksheet('Financial');
|
|
worksheet.WriteColWidth(0, 40);
|
|
worksheet.WriteColWidth(1, 15);
|
|
|
|
worksheet.WriteUTF8Text(0, 0, 'INPUT DATA');
|
|
worksheet.WriteFontStyle(0, 0, [fssBold]);
|
|
|
|
worksheet.WriteUTF8Text(1, 0, 'Interest rate');
|
|
worksheet.WriteNumber(1, 1, INTEREST_RATE, nfPercentage, 1); // B2
|
|
|
|
worksheet.WriteUTF8Text(2, 0, 'Number of payments');
|
|
worksheet.WriteNumber(2, 1, NUMBER_PAYMENTS); // B3
|
|
|
|
worksheet.WriteUTF8Text(3, 0, 'Payment');
|
|
worksheet.WriteCurrency(3, 1, REG_PAYMENT, nfCurrency, 2, '$'); // B4
|
|
|
|
worksheet.WriteUTF8Text(4, 0, 'Present value');
|
|
worksheet.WriteCurrency(4, 1, PRESENT_VALUE, nfCurrency, 2, '$'); // B5
|
|
|
|
worksheet.WriteUTF8Text(5, 0, 'Payment at end (0) or at begin (1)');
|
|
worksheet.WriteNumber(5, 1, ord(PAYMENT_WHEN)); // B6
|
|
|
|
// future value calculation
|
|
fval := FutureValue(INTEREST_RATE, NUMBER_PAYMENTS, REG_PAYMENT, PRESENT_VALUE, PAYMENT_WHEN);
|
|
worksheet.WriteUTF8Text(7, 0, 'CALCULATION OF THE FUTURE VALUE');
|
|
worksheet.WriteFontStyle(7, 0, [fssBold]);
|
|
worksheet.WriteUTF8Text(8, 0, 'Direct calculation');
|
|
worksheet.WriteCurrency(8, 1, fval, nfCurrency, 2, '$');
|
|
|
|
worksheet.WriteUTF8Text(9, 0, 'Worksheet calculation using constants');
|
|
worksheet.WriteNumberFormat(9, 1, nfCurrency, 2, '$');
|
|
worksheet.WriteRPNFormula(9, 1, CreateRPNFormula(
|
|
RPNNumber(INTEREST_RATE,
|
|
RPNNumber(NUMBER_PAYMENTS,
|
|
RPNNumber(REG_PAYMENT,
|
|
RPNNumber(PRESENT_VALUE,
|
|
RPNNumber(ord(PAYMENT_WHEN),
|
|
RPNFunc(fekFV, 5,
|
|
nil))))))));
|
|
worksheet.WriteUTF8Text(10, 0, 'Worksheet calculation using cell values');
|
|
worksheet.WriteNumberFormat(10, 1, nfCurrency, 2, '$');
|
|
worksheet.WriteRPNFormula(10, 1, CreateRPNFormula(
|
|
RPNCellValue('B2', // interest rate
|
|
RPNCellValue('B3', // number of periods
|
|
RPNCellValue('B4', // payment
|
|
RPNCellValue('B5', // present value
|
|
RPNCellValue('B6', // payment at end or at start
|
|
RPNFunc(fekFV, 5, // Call Excel's FV formula
|
|
nil))))))));
|
|
|
|
// present value calculation
|
|
pval := PresentValue(INTEREST_RATE, NUMBER_PAYMENTS, REG_PAYMENT, fval, PAYMENT_WHEN);
|
|
worksheet.WriteUTF8Text(12, 0, 'CALCULATION OF THE PRESENT VALUE');
|
|
worksheet.WriteFontStyle(12, 0, [fssBold]);
|
|
worksheet.WriteUTF8Text(13, 0, 'Direct calculation');
|
|
worksheet.WriteCurrency(13, 1, pval, nfCurrency, 2, '$');
|
|
|
|
worksheet.WriteUTF8Text(14, 0, 'Worksheet calculation using constants');
|
|
worksheet.WriteNumberFormat(14, 1, nfCurrency, 2, '$');
|
|
worksheet.WriteRPNFormula(14, 1, CreateRPNFormula(
|
|
RPNNumber(INTEREST_RATE,
|
|
RPNNumber(NUMBER_PAYMENTS,
|
|
RPNNumber(REG_PAYMENT,
|
|
RPNNumber(fval,
|
|
RPNNumber(ord(PAYMENT_WHEN),
|
|
RPNFunc(fekPV, 5,
|
|
nil))))))));
|
|
Worksheet.WriteUTF8Text(15, 0, 'Worksheet calculation using cell values');
|
|
worksheet.WriteNumberFormat(15, 1, nfCurrency, 2, '$');
|
|
worksheet.WriteRPNFormula(15, 1, CreateRPNFormula(
|
|
RPNCellValue('B2', // interest rate
|
|
RPNCellValue('B3', // number of periods
|
|
RPNCellValue('B4', // payment
|
|
RPNCellValue('B11', // future value
|
|
RPNCellValue('B6', // payment at end or at start
|
|
RPNFunc(fekPV, 5, // Call Excel's PV formula
|
|
nil))))))));
|
|
|
|
// payments calculation
|
|
pmtval := Payment(INTEREST_RATE, NUMBER_PAYMENTS, PRESENT_VALUE, fval, PAYMENT_WHEN);
|
|
worksheet.WriteUTF8Text(17, 0, 'CALCULATION OF THE PAYMENT');
|
|
worksheet.WriteFontStyle(17, 0, [fssBold]);
|
|
worksheet.WriteUTF8Text(18, 0, 'Direct calculation');
|
|
worksheet.WriteCurrency(18, 1, pmtval, nfCurrency, 2, '$');
|
|
|
|
worksheet.WriteUTF8Text(19, 0, 'Worksheet calculation using constants');
|
|
worksheet.WriteNumberFormat(19, 1, nfCurrency, 2, '$');
|
|
worksheet.WriteRPNFormula(19, 1, CreateRPNFormula(
|
|
RPNNumber(INTEREST_RATE,
|
|
RPNNumber(NUMBER_PAYMENTS,
|
|
RPNNumber(PRESENT_VALUE,
|
|
RPNNumber(fval,
|
|
RPNNumber(ord(PAYMENT_WHEN),
|
|
RPNFunc(fekPMT, 5,
|
|
nil))))))));
|
|
Worksheet.WriteUTF8Text(20, 0, 'Worksheet calculation using cell values');
|
|
worksheet.WriteNumberFormat(20, 1, nfCurrency, 2, '$');
|
|
worksheet.WriteRPNFormula(20, 1, CreateRPNFormula(
|
|
RPNCellValue('B2', // interest rate
|
|
RPNCellValue('B3', // number of periods
|
|
RPNCellValue('B5', // present value
|
|
RPNCellValue('B11', // future value
|
|
RPNCellValue('B6', // payment at end or at start
|
|
RPNFunc(fekPMT, 5, // Call Excel's PMT formula
|
|
nil))))))));
|
|
|
|
// number of periods calculation
|
|
nperval := NumberOfPeriods(INTEREST_RATE, REG_PAYMENT, PRESENT_VALUE, fval, PAYMENT_WHEN);
|
|
worksheet.WriteUTF8Text(22, 0, 'CALCULATION OF THE NUMBER OF PAYMENT PERIODS');
|
|
worksheet.WriteFontStyle(22, 0, [fssBold]);
|
|
worksheet.WriteUTF8Text(23, 0, 'Direct calculation');
|
|
worksheet.WriteNumber(23, 1, nperval, nfFixed, 2);
|
|
|
|
worksheet.WriteUTF8Text(24, 0, 'Worksheet calculation using constants');
|
|
worksheet.WriteNumberFormat(24, 1, nfFixed, 2);
|
|
worksheet.WriteRPNFormula(24, 1, CreateRPNFormula(
|
|
RPNNumber(INTEREST_RATE,
|
|
RPNNumber(REG_PAYMENT,
|
|
RPNNumber(PRESENT_VALUE,
|
|
RPNNumber(fval,
|
|
RPNNumber(ord(PAYMENT_WHEN),
|
|
RPNFunc(fekNPER, 5,
|
|
nil))))))));
|
|
Worksheet.WriteUTF8Text(25, 0, 'Worksheet calculation using cell values');
|
|
worksheet.WriteNumberFormat(25, 1, nfFixed, 2);
|
|
worksheet.WriteRPNFormula(25, 1, CreateRPNFormula(
|
|
RPNCellValue('B2', // interest rate
|
|
RPNCellValue('B4', // payment
|
|
RPNCellValue('B5', // present value
|
|
RPNCellValue('B11', // future value
|
|
RPNCellValue('B6', // payment at end or at start
|
|
RPNFunc(fekNPER, 5, // Call Excel's PMT formula
|
|
nil))))))));
|
|
|
|
// interest rate calculation
|
|
rateval := InterestRate(NUMBER_PAYMENTS, REG_PAYMENT, PRESENT_VALUE, fval, PAYMENT_WHEN);
|
|
worksheet.WriteUTF8Text(27, 0, 'CALCULATION OF THE INTEREST RATE');
|
|
worksheet.WriteFontStyle(27, 0, [fssBold]);
|
|
worksheet.WriteUTF8Text(28, 0, 'Direct calculation');
|
|
worksheet.WriteNumber(28, 1, rateval, nfPercentage, 2);
|
|
|
|
worksheet.WriteUTF8Text(29, 0, 'Worksheet calculation using constants');
|
|
worksheet.WriteNumberFormat(29, 1, nfPercentage, 2);
|
|
worksheet.WriteRPNFormula(29, 1, CreateRPNFormula(
|
|
RPNNumber(NUMBER_PAYMENTS,
|
|
RPNNumber(REG_PAYMENT,
|
|
RPNNumber(PRESENT_VALUE,
|
|
RPNNumber(fval,
|
|
RPNNumber(ord(PAYMENT_WHEN),
|
|
RPNFunc(fekRATE, 5,
|
|
nil))))))));
|
|
Worksheet.WriteUTF8Text(30, 0, 'Worksheet calculation using cell values');
|
|
worksheet.WriteNumberFormat(30, 1, nfPercentage, 2);
|
|
worksheet.WriteRPNFormula(30, 1, CreateRPNFormula(
|
|
RPNCellValue('B3', // number of payments
|
|
RPNCellValue('B4', // payment
|
|
RPNCellValue('B5', // present value
|
|
RPNCellValue('B11', // future value
|
|
RPNCellValue('B6', // payment at end or at start
|
|
RPNFunc(fekRATE, 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.Options := workbook.Options + [boReadFormulas];
|
|
workbook.ReadFromFile(AFilename, sfExcel8);
|
|
|
|
worksheet := workbook.GetFirstWorksheet;
|
|
|
|
// Write all cells with contents to the console
|
|
WriteLn('');
|
|
WriteLn('Contents of file "', AFileName, '"');
|
|
WriteLn('');
|
|
|
|
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
|
|
if s2 = '' then
|
|
WriteLn(s1)
|
|
else
|
|
WriteLn(s1+': ':50, s2);
|
|
end;
|
|
|
|
WriteLn;
|
|
WriteLn('Press [ENTER] to close...');
|
|
ReadLn;
|
|
finally
|
|
workbook.Free;
|
|
end;
|
|
end;
|
|
|
|
const
|
|
TestFile='test_fv.xls';
|
|
begin
|
|
WriteFile(TestFile);
|
|
ReadFile(TestFile);
|
|
end.
|
|
|
|
|