lazarus-ccr/components/fpspreadsheet/examples/other/demo_formula_func.pas

313 lines
12 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 a spreadsheet 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,
fpstypes, fpspreadsheet, fpsallformats, fpsexprparser, financemath;
{ Base data used in this demonstration }
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.0; // present value of investment
PAYMENT_WHEN: TPaymentTime = ptEndOfPeriod; // when is the payment made
{------------------------------------------------------------------------------}
{ Adaption of financial functions to usage by fpspreadsheet }
{ The functions are implemented in the unit "financemath.pas". }
{------------------------------------------------------------------------------}
procedure fpsFV(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result.ResFloat := FutureValue(
ArgToFloat(Args[0]), // interest rate
ArgToInt(Args[1]), // number of payments
ArgToFloat(Args[2]), // payment
ArgToFloat(Args[3]), // present value
TPaymentTime(ArgToInt(Args[4])) // payment type
);
end;
procedure fpsPMT(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result.ResFloat := Payment(
ArgToFloat(Args[0]), // interest rate
ArgToInt(Args[1]), // number of payments
ArgToFloat(Args[2]), // present value
ArgToFloat(Args[3]), // future value
TPaymentTime(ArgToInt(Args[4])) // payment type
);
end;
procedure fpsPV(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result.ResFloat := PresentValue(
ArgToFloat(Args[0]), // interest rate
ArgToInt(Args[1]), // number of payments
ArgToFloat(Args[2]), // payment
ArgToFloat(Args[3]), // future value
TPaymentTime(ArgToInt(Args[4])) // payment type
);
end;
procedure fpsNPER(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result.ResFloat := NumberOfPeriods(
ArgToFloat(Args[0]), // interest rate
ArgToFloat(Args[1]), // payment
ArgToFloat(Args[2]), // present value
ArgToFloat(Args[3]), // future value
TPaymentTime(ArgToInt(Args[4])) // payment type
);
end;
procedure fpsRATE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result.ResFloat := InterestRate(
ArgToInt(Args[0]), // number of payments
ArgToFloat(Args[1]), // payment
ArgToFloat(Args[2]), // present value
ArgToFloat(Args[3]), // future value
TPaymentTime(ArgToInt(Args[4])) // payment type
);
end;
{------------------------------------------------------------------------------}
{ Write xls file comparing our own calculations with Excel result }
{------------------------------------------------------------------------------}
procedure WriteFile(AFileName: String);
const
INT_EXCEL_SHEET_FUNC_PV = 56;
INT_EXCEL_SHEET_FUNC_FV = 57;
INT_EXCEL_SHEET_FUNC_NPER = 58;
INT_EXCEL_SHEET_FUNC_PMT = 59;
INT_EXCEL_SHEET_FUNC_RATE = 60;
var
workbook: TsWorkbook;
worksheet: TsWorksheet;
fval, pval, pmtval, nperval, rateval: Double;
formula: String;
fs: TFormatSettings;
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.
The 1st parameter is the data type of the function result ('F'=float)
The 2nd parameter shows the data types of the arguments ('F=float, 'I'=integer)
The 3rd parameter is the Excel ID needed when writing to xls files. (see
"OpenOffice Documentation of Microsoft Excel File Format", section 3.11)
The 4th parameter is the address of the function to be used for calculation. }
RegisterFunction('FV', 'F', 'FIFFI', INT_EXCEL_SHEET_FUNC_FV, @fpsFV);
RegisterFunction('PMT', 'F', 'FIFFI', INT_EXCEL_SHEET_FUNC_PMT, @fpsPMT);
RegisterFunction('PV', 'F', 'FIFFI', INT_EXCEL_SHEET_FUNC_PV, @fpsPV);
RegisterFunction('NPER', 'F', 'FFFFI', INT_EXCEL_SHEET_FUNC_NPER, @fpsNPER);
RegisterFunction('RATE', 'F', 'IFFFI', INT_EXCEL_SHEET_FUNC_RATE, @fpsRATE);
// The formula parser requires a point as decimals separator.
fs := DefaultFormatSettings;
fs.DecimalSeparator := '.';
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.WriteNumberFormat(9, 1, nfCurrency, 2, '$');
formula := Format('FV(%f,%d,%f,%f,%d)',
[1.0*INTEREST_RATE, NUMBER_PAYMENTS, 1.0*REG_PAYMENT, 1.0*PRESENT_VALUE, ord(PAYMENT_WHEN)], fs
);
worksheet.WriteFormula(9, 1, formula);
worksheet.WriteUTF8Text(10, 0, 'Worksheet calculation using cell values');
worksheet.WriteNumberFormat(10, 1, nfCurrency, 2, '$');
worksheet.WriteFormula(10, 1, 'FV(B2,B3,B4,B5,B6)');
// 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');
formula := Format('PV(%f,%d,%f,%f,%d)',
[1.0*INTEREST_RATE, NUMBER_PAYMENTS, 1.0*REG_PAYMENT, fval, ord(PAYMENT_WHEN)], fs
);
worksheet.WriteNumberFormat(14, 1, nfCurrency, 2, '$');
worksheet.WriteFormula(14, 1, formula);
Worksheet.WriteUTF8Text(15, 0, 'Worksheet calculation using cell values');
worksheet.WriteNumberFormat(15, 1, nfCurrency, 2, '$');
worksheet.WriteFormula(15, 1, 'PV(B2,B3,B4,B11,B6)');
// 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, '$');
formula := Format('PMT(%g,%d,%g,%g,%d)',
[INTEREST_RATE, NUMBER_PAYMENTS, PRESENT_VALUE, fval, ord(PAYMENT_WHEN)], fs
);
worksheet.WriteFormula(19, 1, formula);
Worksheet.WriteUTF8Text(20, 0, 'Worksheet calculation using cell values');
worksheet.WriteNumberFormat(20, 1, nfCurrency, 2, '$');
worksheet.WriteFormula(20, 1, 'PMT(B2,B3,B5,B11,B6)');
// 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);
formula := Format('NPER(%g,%g,%g,%g,%d)',
[1.0*INTEREST_RATE, 1.0*REG_PAYMENT, 1.0*PRESENT_VALUE, fval, ord(PAYMENT_WHEN)], fs
);
worksheet.WriteFormula(24, 1, formula);
Worksheet.WriteUTF8Text(25, 0, 'Worksheet calculation using cell values');
worksheet.WriteNumberFormat(25, 1, nfFixed, 2);
worksheet.WriteFormula(25, 1, 'NPER(B2,B4,B5,B11,B6)');
// 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);
formula := Format('RATE(%d,%g,%g,%g,%d)',
[NUMBER_PAYMENTS, 1.0*REG_PAYMENT, 1.0*PRESENT_VALUE, fval, ord(PAYMENT_WHEN)], fs
);
worksheet.WriteFormula(29, 1, formula);
Worksheet.WriteUTF8Text(30, 0, 'Worksheet calculation using cell values');
worksheet.WriteNumberFormat(30, 1, nfPercentage, 2);
worksheet.WriteFormula(30, 1, 'RATE(B3,B4,B5,B11,B6)');
workbook.WriteToFile(AFileName, 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, boAutoCalc];
workbook.ReadFromFile(AFilename);
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;
finally
workbook.Free;
end;
end;
const
TestFile='test_user_formula.xlsx'; // Format depends on extension selected
// !!!! ods not working yet !!!!
begin
WriteLn('This demo registers user-defined functions for financial calculations');
WriteLn('and writes and reads the corresponding spreadsheet file.');
WriteLn;
WriteFile(TestFile);
ReadFile(TestFile);
WriteLn;
WriteLn('Open the file in Excel or OpenOffice/LibreOffice.');
WriteLn('Press [ENTER] to close...');
ReadLn;
end.