Функция RATE() на основе PHPExcel, возвращающая NAN()

У меня есть этот код: http://pastebin.com/Sd9WKZFr

Когда я вызываю что-то вроде rate(60, -6000, 120000), он возвращает мне результат NAN, но та же функция в MS Excel возвращает мне 0,04678.... У меня та же проблема с -5000, -4000, -3000 и -2000.

Когда я отлаживаю код, я вижу, что в итерации 8/9 строка номер 29 начинает возвращать результат NAN, в результате чего все остальные результаты также становятся NAN.

НО, когда я вызываю что-то вроде rate(60, -1000, 120000), он возвращает мне float -0.02044..., точно такой же результат MS Excel.

Я уже пытался преобразовать все математические вычисления в функции BCMath, но таким образом результаты -6000 неверны (-1,0427... вместо 0,04678. ..), но при использовании -1000 результат правильный, соответствующий результату excel.

Есть ли способ заставить его работать правильно?

Заранее спасибо за любой полезный взгляд на это.


person sdlins    schedule 07.04.2012    source источник
comment
это всего лишь догадки, это выглядит в первую очередь как расчетная задача. - возможно ли, что $rate может стать отрицательным, поэтому log() не определен в строке 29? - правильно ли установлены брекеты?   -  person worenga    schedule 07.04.2012
comment
Привет @mightyuhu, синтаксических ошибок нет, потому что при использовании -1000 все работает нормально. В то же время это функция стабильного phpexcel. В любом случае спасибо.   -  person sdlins    schedule 07.04.2012
comment
@sidtj @mightyuhu прав, rate(60, -6000, 120000) дает промежуточный результат Rate ‹ 1 (вы уже определили это на 8/9-й итерации). Вам нужно работать над своим алгоритмом   -  person chris neilsen    schedule 07.04.2012
comment
Извините, кажется, я неправильно понял @mightyuhu. Но я действительно не могу понять, что мне делать. У меня нет даже минимального представления. Не могли бы вы помочь? Что именно я должен изменить в своем алгоритме? И почему это прекрасно работает в Excel? (=скорость(60, -6000, 120000)   -  person sdlins    schedule 07.04.2012


Ответы (2)


Мне нужно будет провести несколько тестов, чтобы убедиться в отсутствии побочных эффектов в других ситуациях; но следующее выглядит так, как будто это может решить эту проблему, и, безусловно, вычисляет правильное значение скорости для ваших аргументов RATE(60, -6000, 120000) стабилизируется на уровне 0,046781916422493 на итерации 15.

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 (($nper * abs($pmt)) > ($pv - $fv))
            $x1 = abs($x1);

        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() 
person Mark Baker    schedule 07.04.2012
comment
Привет, Бейкер, я собирался отправить вам эту ссылку на форуме phpexcel, когда увидел, кто ее отправил: вы сами. Спасибо большое. Я попробую в разных обстоятельствах, прежде чем принять ваш ответ. Спасибо. - person sdlins; 07.04.2012
comment
Уже проведено некоторое тестирование: это не работает с вашим примером скорости (60, -1000, 120000), исследуйте дальше - person Mark Baker; 07.04.2012
comment
Как жаль. Используя 60, -6000, 120000, я получаю 0,046781916422493, тот же результат LibreOffice Calc (Excel не может этого сделать, возвращая #ЧИСЛО!). Но более низкие значения (-1000, -2000 и т. д.) этой функции, теперь использующей abs(), действительно возвращают неправильные результаты. Спасибо за ваши ценные усилия. - person sdlins; 07.04.2012
comment
@sidtj — см. отредактированную версию для изменения, которое будет работать с обоими вашими примерами. - person Mark Baker; 07.04.2012
comment
Большое спасибо, Баркер! Я пробовал со многими значениями, и это работает до максимума -8000. Значения -9000, -10000 и далее пока не работает. Возможно, вы захотите исправить это для следующего выпуска phpexcel. Но для моих нужд сейчас работает нормально. Спасибо еще раз. - person sdlins; 08.04.2012
comment
В долгосрочной перспективе я, вероятно, перейду на использование Ньютона Рафсона, а не на секущую; но в то же время это простое изменение появится в следующем выпуске PHPExcel... все еще есть случаи, которые оно не разрешает, но оно работает с большим количеством случаев, чем неизмененная версия. - person Mark Baker; 08.04.2012
comment
Ok. PHPExcel — отличный кусок кода! Поздравляем и пусть все вы продолжаете хорошую работу! - person sdlins; 08.04.2012

Я бы не советовал использовать метод секущей для определения внутренней нормы доходности, так как он требует больше времени, чем другие предпочтительные итерационные методы, такие как метод Ньютона-Рафсона. Из кода кажется, что установка максимум 128 итераций - пустая трата времени.

Использование метода Ньютона Рафсона для нахождения СТАВКИ с одним из двух TVM требует всего 3 итерации

TVM Eq. 1: PV(1+i)^N + PMT(1+i*type)[(1+i)^N -1]/i + FV = 0

f(i) = 0 + -6000 * (1 + i * 0) [(1+i)^60 - 1)]/i + 120000 * (1+i)^60

f'(i) = (-6000 * ( 60 * i * (1 + i)^(59+0) - (1 + i)^60) + 1) / (i * i)) + 60 * 120000 * (1+0.05)^59

i0 = 0.05
f(i1) = 120000
f'(i1) = 42430046.1459
i1 = 0.05 - 120000/42430046.1459 = 0.0471718154728
Error Bound = 0.0471718154728 - 0.05 = 0.002828 > 0.000001

i1 = 0.0471718154728
f(i2) = 12884.8972
f'(i2) = 33595275.7358
i2 = 0.0471718154728 - 12884.8972/33595275.7358 = 0.0467882824629
Error Bound = 0.0467882824629 - 0.0471718154728 = 0.000384 > 0.000001

i2 = 0.0467882824629
f(i3) = 206.9714
f'(i3) = 32520602.801
i3 = 0.0467882824629 - 206.9714/32520602.801 = 0.0467819181458
Error Bound = 0.0467819181458 - 0.0467882824629 = 6.0E-6 > 0.000001

i3 = 0.0467819181458
f(i4) = 0.056
f'(i4) = 32503002.4159
i4 = 0.0467819181458 - 0.056/32503002.4159 = 0.0467819164225
Error Bound = 0.0467819164225 - 0.0467819181458 = 0 < 0.000001
IRR = 4.68%


TVM Eq. 2: PV + PMT(1+i*type)[1-{(1+i)^-N}]/i + FV(1+i)^-N = 0

f(i) = 120000 + -6000 * (1 + i * 0) [1 - (1+i)^-60)]/i + 0 * (1+i)^-60

f'(i) = (--6000 * (1+i)^-60 * ((1+i)^60 - 60 * i - 1) /(i*i)) + (0 * -60 * (1+i)^(-60-1))

i0 = 0.05
f(i1) = 6424.2628
f'(i1) = 1886058.972
i1 = 0.05 - 6424.2628/1886058.972 = 0.0465938165535
Error Bound = 0.0465938165535 - 0.05 = 0.003406 > 0.000001

i1 = 0.0465938165535
f(i2) = -394.592
f'(i2) = 2081246.2069
i2 = 0.0465938165535 - -394.592/2081246.2069 = 0.046783410646
Error Bound = 0.046783410646 - 0.0465938165535 = 0.00019 > 0.000001

i2 = 0.046783410646
f(i3) = 3.1258
f'(i3) = 2069722.0554
i3 = 0.046783410646 - 3.1258/2069722.0554 = 0.0467819004105
Error Bound = 0.0467819004105 - 0.046783410646 = 2.0E-6 > 0.000001

i3 = 0.0467819004105
f(i4) = -0.0335
f'(i4) = 2069813.5309
i4 = 0.0467819004105 - -0.0335/2069813.5309 = 0.0467819165937
Error Bound = 0.0467819165937 - 0.0467819004105 = 0 < 0.000001
IRR = 4.68%
person Community    schedule 08.04.2012