diff --git a/components/fpspreadsheet/examples/other/test_formula_func.lps b/components/fpspreadsheet/examples/other/test_formula_func.lps index c59622bcc..39ac60b35 100644 --- a/components/fpspreadsheet/examples/other/test_formula_func.lps +++ b/components/fpspreadsheet/examples/other/test_formula_func.lps @@ -4,194 +4,204 @@ - + - - - + + + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + + - - + + - - + + - + diff --git a/components/fpspreadsheet/examples/other/test_formula_func.pas b/components/fpspreadsheet/examples/other/test_formula_func.pas index 5995860c8..9412e09f9 100644 --- a/components/fpspreadsheet/examples/other/test_formula_func.pas +++ b/components/fpspreadsheet/examples/other/test_formula_func.pas @@ -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