nfTlWNl rn Ѻq$³*%yNk9SVBŹ!Sn6ASOgzm32)dck CӝINg?3~)?5_.z/OX{;k0GrJsf]ZV hHWgq!`kk:џU : =-f06+ꓻ=m'Y7#Β5! RmtNnHCoQ1g CӝINgU@v_=6x:eES1MP㶠Riњ!QHbL@"^>jU ስB%{̷<|CY5΍ ĢQ$3w@+L]44< W)KOjx`]7 ) |>fM븮?|>`BJ[h\c;ۄqVuӯt)&Nk+@8o0\au^ju[ K-: ĢQ$3Pk#hԢ۷,F!)p.EAjUt[ 5|Eވ@6jwc8JHMh iw\.v ĢQ$3z&gc*0%zy f@ ۽S-k7ZR:ns^G'lN}?imCDz܂ٗ?aiMxht3 9uc቟s3AWҲ`BrvxM) S2al]3_6xE\YԵvsliso^ĊrGwIWtCa 6Z[ dT6rj{P1[U63:9X+URf~~\ '(+ȼ 5>vJئaW\3(W 3Mjwu?|EXѩ ?v%3Yϡjd|O)[S(.d Y\Tb^q8^3rduH|SbNc6fRK*ǛtQ̔Dۓyҋpkt$P\ |M/Kf/>U& v>R];AVuN5\ 2_9s`&r5SC`^k;/i+|:SҲ̌1=憟]v~y0Jl=ͪϘs#Uuy !':ѾA)EuӐ #4k0S^86**QZnJQ=wFt%4FtCEP׃7v"pWNw!}x<)m&9M+ 1ֽ"&BWQ)k["Z}29U3 `Vʉjqź%rT Àn\"jQ6C]sBCucMo˾vC):AVVD9=׀~TC>aG@Z 撊Wgk#q2T/TPȣ[!?Z\wD+rc6fRK*ǛtQg'.MڨBĊA6PfLLtNCf Xpt-uiZ9AȖ88] ܱ Pꌙx15 0y'5}P; get!oD0'6 ˰}`` pUNd[;<h3:9X+URf~~\ '(+ȼ 5>vJئaW\3(?jkSK1)&lbI./'FN- :Q`}s˳7;W0Le .ڗP:4]$ ,|*l,'3Sfd"s$^Nr$W 3Mjwu?{-@rO#tE3,8p:?5njqź%rT ]EP.`>*|oMG|8X=0!ے ob*zy̷K2]̌1=憟]v~y0Jl=ͪϘs#Uuy !':#`.6ɋ&& yeD@k|ױKI:u{0@9cN Y\Tb^qm|p^%L 5b9& 0HE6KZwv9q"Щcig6 yvqj~nim6 N*c-P^?//4ƅ,.ݨt00ʴ;D%  ->'\A N{ˇٝVsׇ>xQ@J/\5d]CNLL{9@lҏ$edx5d6}Y^<(-ZQCh .A;W_~{ewS4%bѪ[Na{"}+*%B<lUx? t6ΉpMW}wr$cl&N dɜ=3>"3ʡQWxFJ@>7xHD$Uy ( "\'O}[V4I?GexG̊:놃6\t0ćǡ;V._X1eb:v5 ddrC*ZJSUNEHfN%~ TrD%k e'<;rXG E;]"C+%YnTAH3ݶz?Cv-H ; Z6-8i!'T_}sdH#HFZ\x8NoK 1cAuk4zA ĢQ$3d7Y~ҭ-4sng of the investment. * If the first value is a cost or payment, it must be a negative value. * All succeeding payments are discounted based on a 365-day year. * The series of values must contain at least one positive value and one negative value. * @param mixed[] $dates A schedule of payment dates that corresponds to the cash flow payments. * The first payment date indicates the beginning of the schedule of payments. * All other dates must be later than this date, but they may occur in any order. * * @return float|string */ public static function presentValue($rate, $values, $dates) { return self::xnpvOrdered($rate, $values, $dates, true); } private static function bothNegAndPos(bool $neg, bool $pos): bool { return $neg && $pos; } /** * @param mixed $values * @param mixed $dates */ private static function xirrPart1(&$values, &$dates): string { $values = Functions::flattenArray($values); $dates = Functions::flattenArray($dates); $valuesIsArray = count($values) > 1; $datesIsArray = count($dates) > 1; if (!$valuesIsArray && !$datesIsArray) { return ExcelError::NA(); } if (count($values) != count($dates)) { return ExcelError::NAN(); } $datesCount = count($dates); for ($i = 0; $i < $datesCount; ++$i) { try { $dates[$i] = DateTimeExcel\Helpers::getDateValue($dates[$i]); } catch (Exception $e) { return $e->getMessage(); } } return self::xirrPart2($values); } private static function xirrPart2(array &$values): string { $valCount = count($values); $foundpos = false; $foundneg = false; for ($i = 0; $i < $valCount; ++$i) { $fld = $values[$i]; if (!is_numeric($fld)) { return ExcelError::VALUE(); } elseif ($fld > 0) { $foundpos = true; } elseif ($fld < 0) { $foundneg = true; } } if (!self::bothNegAndPos($foundneg, $foundpos)) { return ExcelError::NAN(); } return ''; } /** * @return float|string */ private static function xirrPart3(array $values, array $dates, float $x1, float $x2) { $f = self::xnpvOrdered($x1, $values, $dates, false); if ($f < 0.0) { $rtb = $x1; $dx = $x2 - $x1; } else { $rtb = $x2; $dx = $x1 - $x2; } $rslt = ExcelError::VALUE(); for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { $dx *= 0.5; $x_mid = $rtb + $dx; $f_mid = (float) self::xnpvOrdered($x_mid, $values, $dates, false); if ($f_mid <= 0.0) { $rtb = $x_mid; } if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { $rslt = $x_mid; break; } } return $rslt; } /** * @return float|string */ private static function xirrBisection(array $values, array $dates, float $x1, float $x2) { $rslt = ExcelError::NAN(); for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { $rslt = ExcelError::NAN(); $f1 = self::xnpvOrdered($x1, $values, $dates, false, true); $f2 = self::xnpvOrdered($x2, $values, $dates, false, true); if (!is_numeric($f1) || !is_numeric($f2)) { break; } $f1 = (float) $f1; $f2 = (float) $f2; if (abs($f1) < self::FINANCIAL_PRECISION && abs($f2) < self::FINANCIAL_PRECISION) { break; } if ($f1 * $f2 > 0) { break; } $rslt = ($x1 + $x2) / 2; $f3 = self::xnpvOrdered($rslt, $values, $dates, false, true); if (!is_float($f3)) { break; } if ($f3 * $f1 < 0) { $x2 = $rslt; } else { $x1 = $rslt; } if (abs($f3) < self::FINANCIAL_PRECISION) { break; } } return $rslt; } /** * @param mixed $rate * @param mixed $values * @param mixed $dates * * @return float|string */ private static function xnpvOrdered($rate, $values, $dates, bool $ordered = true, bool $capAtNegative1 = false) { $rate = Functions::flattenSingleValue($rate); $values = Functions::flattenArray($values); $dates = Functions::flattenArray($dates); $valCount = count($values); try { self::validateXnpv($rate, $values, $dates); if ($capAtNegative1 && $rate <= -1) { $rate = -1.0 + 1.0E-10; } $date0 = DateTimeExcel\Helpers::getDateValue($dates[0]); } catch (Exception $e) { return $e->getMessage(); } $xnpv = 0.0; for ($i = 0; $i < $valCount; ++$i) { if (!is_numeric($values[$i])) { return ExcelError::VALUE(); } try { $datei = DateTimeExcel\Helpers::getDateValue($dates[$i]); } catch (Exception $e) { return $e->getMessage(); } if ($date0 > $datei) { $dif = $ordered ? ExcelError::NAN() : -((int) DateTimeExcel\Difference::interval($datei, $date0, 'd')); } else { $dif = Functions::scalar(DateTimeExcel\Difference::interval($date0, $datei, 'd')); } if (!is_numeric($dif)) { return $dif; } if ($rate <= -1.0) { $xnpv += -abs($values[$i]) / (-1 - $rate) ** ($dif / 365); } else { $xnpv += $values[$i] / (1 + $rate) ** ($dif / 365); } } return is_finite($xnpv) ? $xnpv : ExcelError::VALUE(); } /** * @param mixed $rate */ private static function validateXnpv($rate, array $values, array $dates): void { if (!is_numeric($rate)) { throw new Exception(ExcelError::VALUE()); } $valCount = count($values); if ($valCount != count($dates)) { throw new Exception(ExcelError::NAN()); } if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) { throw new Exception(ExcelError::NAN()); } } }