Home » Php » php – Recreate Excel RATE function using Newton's Method

# php – Recreate Excel RATE function using Newton's Method

Questions:

I’m working on converting a mortgage calculator in PHP, but I don’t necessarily need a PHP solution. I’m looking for the logic needed to replicate the Excel `RATE` function. I’ve found a solution which uses bisection, and if worse comes to worse, I use that.

I know someone out there in the interwebs world has knowledge of such a function, so I’d love to have an easy answer instead of creating a solution from scratch.

References:

Thanks

Implementation of the MS Excel RATE() function using the secant method (a finite difference approximation of Newton’s method) taken from PHPExcel:

``````define('FINANCIAL_MAX_ITERATIONS', 128);
define('FINANCIAL_PRECISION', 1.0e-08);

function RATE(\$nper, \$pmt, \$pv, \$fv = 0.0, \$type = 0, \$guess = 0.1) {

\$rate = \$guess;
if (abs(\$rate) < FINANCIAL_PRECISION) {
\$y = \$pv * (1 + \$nper * \$rate) + \$pmt * (1 + \$rate * \$type) * \$nper + \$fv;
} else {
\$f = exp(\$nper * log(1 + \$rate));
\$y = \$pv * \$f + \$pmt * (1 / \$rate + \$type) * (\$f - 1) + \$fv;
}
\$y0 = \$pv + \$pmt * \$nper + \$fv;
\$y1 = \$pv * \$f + \$pmt * (1 / \$rate + \$type) * (\$f - 1) + \$fv;

// find root by secant method
\$i  = \$x0 = 0.0;
\$x1 = \$rate;
while ((abs(\$y0 - \$y1) > FINANCIAL_PRECISION) && (\$i < FINANCIAL_MAX_ITERATIONS)) {
\$rate = (\$y1 * \$x0 - \$y0 * \$x1) / (\$y1 - \$y0);
\$x0 = \$x1;
\$x1 = \$rate;

if (abs(\$rate) < FINANCIAL_PRECISION) {
\$y = \$pv * (1 + \$nper * \$rate) + \$pmt * (1 + \$rate * \$type) * \$nper + \$fv;
} else {
\$f = exp(\$nper * log(1 + \$rate));
\$y = \$pv * \$f + \$pmt * (1 / \$rate + \$type) * (\$f - 1) + \$fv;
}

\$y0 = \$y1;
\$y1 = \$y;
++\$i;
}
return \$rate;
}   //  function RATE()
``````

I tried to use the code above, but the results simply aren´t the same as Excel (or Google Spreadsheet).

I dont know if you need to implement this function yet, but in any case, I looked at how this algorithm was built and even though I was not able to access the excel source code (or the google worksheet) I found that this is not a simple calculation. About this math, more can be read here:

https://brownmath.com/bsci/loan.htm#Eq8

The function, in PHP, may be something like this:

``````function rate(\$nprest, \$vlrparc, \$vp, \$guess = 0.25) {
\$maxit = 100;
\$precision = 14;
\$guess = round(\$guess,\$precision);
for (\$i=0 ; \$i<\$maxit ; \$i++) {
\$divdnd = \$vlrparc - ( \$vlrparc * (pow(1 + \$guess , -\$nprest)) ) - (\$vp * \$guess);
\$divisor = \$nprest * \$vlrparc * pow(1 + \$guess , (-\$nprest - 1)) - \$vp;
\$newguess = \$guess - ( \$divdnd / \$divisor );
\$newguess = round(\$newguess, \$precision);
if (\$newguess == \$guess) {
return \$newguess;
} else {
\$guess = \$newguess;
}
}
return null;
}
``````

For Laravel use the same function but you remove define

``````define('FINANCIAL_MAX_ITERATIONS', 128);
define('FINANCIAL_PRECISION', 1.0e-08);

``````

and financial_max_iterations = 20; -> same excel

The code is:

``````function RATE(\$nper, \$pmt, \$pv, \$fv = 0.0, \$type = 0, \$guess = 0.1) {
\$financial_max_iterations = 20;
\$financial_precision = 0.00000008;

\$rate = \$guess;
if (abs(\$rate) < \$financial_precision) {
\$y = \$pv * (1 + \$nper * \$rate) + \$pmt * (1 + \$rate * \$type) * \$nper + \$fv;
} else {
\$f = exp(\$nper * log(1 + \$rate));
\$y = \$pv * \$f + \$pmt * (1 / \$rate + \$type) * (\$f - 1) + \$fv;
}
\$y0 = \$pv + \$pmt * \$nper + \$fv;
\$y1 = \$pv * \$f + \$pmt * (1 / \$rate + \$type) * (\$f - 1) + \$fv;

// find root by secant method
\$i  = \$x0 = 0.0;
\$x1 = \$rate;
while ((abs(\$y0 - \$y1) > \$financial_precision) && (\$i < \$financial_max_iterations)) {
\$rate = (\$y1 * \$x0 - \$y0 * \$x1) / (\$y1 - \$y0);
\$x0 = \$x1;
\$x1 = \$rate;

if (abs(\$rate) < \$financial_precision) {
\$y = \$pv * (1 + \$nper * \$rate) + \$pmt * (1 + \$rate * \$type) * \$nper + \$fv;
} else {
\$f = exp(\$nper * log(1 + \$rate));
\$y = \$pv * \$f + \$pmt * (1 / \$rate + \$type) * (\$f - 1) + \$fv;
}

\$y0 = \$y1;
\$y1 = \$y;
++\$i;
}
return \$rate;
}

``````

it worked for me

TL;DR: Here’s a SQL Server version. It doesn’t work for some values, and the PHP code above will probably fail for the same values.

LONG ANSWER: I needed a RATE function for SQL Server. Using the PHPExcel answer above, and using https://charlottecredittechnology.blogspot.com/2013/05/sql-2008-excel-like-rate-function-part.html I wrote a SQL Server scalar function:

``````ALTER function [dbo].[Rate](
@nper integer, @pmt float, @pv float, @fv float, @type bit = 0, @guess float = 0.1
) returns numeric(38,10) as
/*
Calculate the effective interest rate of a sequence of regular payments.
*/
begin
declare @returns numeric(38,10) = 0;
if @type is null set @type = 0;

declare @i integer;
declare @rate float = @guess;
declare @FINANCIAL_MAX_ITERATIONS integer = 100;
declare @FINANCIAL_PRECISION float = 0.0000001;
declare @y float, @y0 float, @y1 float, @f float, @x0 float, @x1 float;

set @rate = @guess;
if Abs(@rate) < @FINANCIAL_PRECISION
begin
set @f = 0;
set @y = @pv * ([email protected]*@rate) + @pmt * ([email protected]*@type) * @nper + @fv;
end
else
begin
set @f = Exp(@nper * Log([email protected]));
set @y = @pv * @f + @pmt * (1/@rate + @type) * (@f-1) + @fv;
end;
set @y0 = @pv + @pmt * @nper + @fv;
set @y1 = @pv * @f + @pmt * (1/@rate + @type) * (@f-1) + @fv;

-- Newton secant method.
set @i = 0;
set @x0 = 0;
set @x1 = @rate;
while Abs(@[email protected]) > @FINANCIAL_PRECISION and @i < @FINANCIAL_MAX_ITERATIONS
begin
set @rate = (@y1 * @x0 - @y0 * @x1) / (@[email protected]);
set @x0 = @x1;
set @x1 = @rate;
if Abs(@rate) < @FINANCIAL_PRECISION
begin
set @y = @pv * ([email protected]*@rate) + @pmt * ([email protected]*@type) * @nper + @fv;
end
else
begin
set @f = Exp(@nper * Log([email protected]));
set @y = @pv * @f + @pmt * (1/@rate + @type) * (@f-1) + @fv;
end;
set @y0 = @y1;
set @y1 = @y;
set @i = @i + 1;
end;
return Convert(numeric(38,10), @rate);
end;
``````

Unfortunately it does not always work. Here’s the results of some tests I put together and checked using Excel:

``````-- (1) OK
select dbo.RATE(4*12, -200, 8000, 0, default, default) * 12  -- SQL formula
0.0924 (9.24%)                                               -- SQL result
=RATE(4*12, -200, 8000, 0) * 12                              -- Excel formula
9.24%                                                        -- Excel result

-- (2) OK
select dbo.RATE(12, -1000, 12000, 0, default, default) * 12  -- SQL formula
0 (0%)                                                       -- SQL result
=RATE(12, -1000, 12000, 0) * 12                              -- Excel formula
0%                                                           -- Excel result

-- (3) OK
select dbo.RATE(30, -400, 4000, 0, 1, default)    -- SQL formula
0.10496 (10.496%)                                 -- SQL result
=RATE(30, -400, 4000, 0, 1)                       -- Excel formula
10.4964%                                          -- Excel result

-- (4) OK
select dbo.RATE(120, 28.1, -2400, 0, default, default)  -- SQL formula
0.0059905810 (0.599%)                                   -- SQL result
=RATE(120, 28.1, -2400, 0)                              -- Excel formula
0.5991%                                                 -- Excel result

-- (5) OK
select dbo.RATE(10, -1000, 10000, -10000, default, default)  -- SQL formula
0.1 (10%)                                                    -- SQL result
=RATE(10, -1000, 10000, -10000)                              -- Excel formula
10%                                                          -- Excel result

-- (6) WRONG ANSWER (unless you set @guess to 0.01)
select dbo.RATE(475, -1022.93, 272779.21, 0, default, default)  -- SQL formula
0                                                               -- SQL result
=RATE(475, -1022.93, 272779.21, 0, 0)                           -- Excel formula
0.2716%                                                         -- Excel result

-- (7) ERROR
select dbo.RATE(252, -29002.85, 2500000, 0, default, default)  -- SQL formula
invalid floating point operation                               -- SQL result
=RATE(252, -29002.85, 2500000, 0)                              -- Excel formula
1.0833%                                                        -- Excel result

-- (8) OK
select dbo.RATE(24, -46.14, 1000, 0, default, default)    -- SQL formula
0.0083244385 (0.83244%)                                   -- SQL result
=RATE(24, -46.14, 1000, 0)                                -- Excel formula
0.8324%                                                   -- Excel result
``````

Tests (7) and (8) were taken from RATE Function from EXCEL in Swift providing different results and look for the answer using the Newton-Raphson method.