ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
NonPeriodic.php
Go to the documentation of this file.
1<?php
2
4
8
10{
12
13 const FINANCIAL_PRECISION = 1.0e-08;
14
32 public static function rate($values, $dates, $guess = 0.1)
33 {
34 $rslt = self::xirrPart1($values, $dates);
35 if ($rslt) {
36 return $rslt;
37 }
38
39 // create an initial range, with a root somewhere between 0 and guess
40 $guess = Functions::flattenSingleValue($guess);
41 $x1 = 0.0;
42 $x2 = $guess ?: 0.1;
43 $f1 = self::xnpvOrdered($x1, $values, $dates, false);
44 $f2 = self::xnpvOrdered($x2, $values, $dates, false);
45 $found = false;
46 for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
47 if (!is_numeric($f1) || !is_numeric($f2)) {
48 break;
49 }
50 if (($f1 * $f2) < 0.0) {
51 $found = true;
52
53 break;
54 } elseif (abs($f1) < abs($f2)) {
55 $f1 = self::xnpvOrdered($x1 += 1.6 * ($x1 - $x2), $values, $dates, false);
56 } else {
57 $f2 = self::xnpvOrdered($x2 += 1.6 * ($x2 - $x1), $values, $dates, false);
58 }
59 }
60 if (!$found) {
61 return Functions::NAN();
62 }
63
64 return self::xirrPart3($values, $dates, $x1, $x2);
65 }
66
89 public static function presentValue($rate, $values, $dates)
90 {
91 return self::xnpvOrdered($rate, $values, $dates, true);
92 }
93
94 private static function bothNegAndPos($neg, $pos)
95 {
96 return $neg && $pos;
97 }
98
99 private static function xirrPart1(&$values, &$dates)
100 {
101 if ((!is_array($values)) && (!is_array($dates))) {
102 return Functions::NA();
103 }
105 $dates = Functions::flattenArray($dates);
106 if (count($values) != count($dates)) {
107 return Functions::NAN();
108 }
109
110 $datesCount = count($dates);
111 for ($i = 0; $i < $datesCount; ++$i) {
112 try {
113 $dates[$i] = DateTimeExcel\Helpers::getDateValue($dates[$i]);
114 } catch (Exception $e) {
115 return $e->getMessage();
116 }
117 }
118
119 return self::xirrPart2($values);
120 }
121
122 private static function xirrPart2(&$values)
123 {
124 $valCount = count($values);
125 $foundpos = false;
126 $foundneg = false;
127 for ($i = 0; $i < $valCount; ++$i) {
128 $fld = $values[$i];
129 if (!is_numeric($fld)) {
130 return Functions::VALUE();
131 } elseif ($fld > 0) {
132 $foundpos = true;
133 } elseif ($fld < 0) {
134 $foundneg = true;
135 }
136 }
137 if (!self::bothNegAndPos($foundneg, $foundpos)) {
138 return Functions::NAN();
139 }
140
141 return '';
142 }
143
144 private static function xirrPart3($values, $dates, $x1, $x2)
145 {
146 $f = self::xnpvOrdered($x1, $values, $dates, false);
147 if ($f < 0.0) {
148 $rtb = $x1;
149 $dx = $x2 - $x1;
150 } else {
151 $rtb = $x2;
152 $dx = $x1 - $x2;
153 }
154
155 $rslt = Functions::VALUE();
156 for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
157 $dx *= 0.5;
158 $x_mid = $rtb + $dx;
159 $f_mid = self::xnpvOrdered($x_mid, $values, $dates, false);
160 if ($f_mid <= 0.0) {
161 $rtb = $x_mid;
162 }
163 if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
164 $rslt = $x_mid;
165
166 break;
167 }
168 }
169
170 return $rslt;
171 }
172
173 private static function xnpvOrdered($rate, $values, $dates, $ordered = true)
174 {
175 $rate = Functions::flattenSingleValue($rate);
177 $dates = Functions::flattenArray($dates);
178 $valCount = count($values);
179
180 try {
181 self::validateXnpv($rate, $values, $dates);
182 $date0 = DateTimeExcel\Helpers::getDateValue($dates[0]);
183 } catch (Exception $e) {
184 return $e->getMessage();
185 }
186
187 $xnpv = 0.0;
188 for ($i = 0; $i < $valCount; ++$i) {
189 if (!is_numeric($values[$i])) {
190 return Functions::VALUE();
191 }
192
193 try {
194 $datei = DateTimeExcel\Helpers::getDateValue($dates[$i]);
195 } catch (Exception $e) {
196 return $e->getMessage();
197 }
198 if ($date0 > $datei) {
199 $dif = $ordered ? Functions::NAN() : -((int) DateTimeExcel\Difference::interval($datei, $date0, 'd'));
200 } else {
201 $dif = DateTimeExcel\Difference::interval($date0, $datei, 'd');
202 }
203 if (!is_numeric($dif)) {
204 return $dif;
205 }
206 $xnpv += $values[$i] / (1 + $rate) ** ($dif / 365);
207 }
208
209 return is_finite($xnpv) ? $xnpv : Functions::VALUE();
210 }
211
212 private static function validateXnpv($rate, $values, $dates): void
213 {
214 if (!is_numeric($rate)) {
215 throw new Exception(Functions::VALUE());
216 }
217 $valCount = count($values);
218 if ($valCount != count($dates)) {
219 throw new Exception(Functions::NAN());
220 }
221 if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) {
222 throw new Exception(Functions::NAN());
223 }
224 }
225}
An exception for terminatinating execution or to throw for unit testing.
static interval($startDate, $endDate, $unit='D')
DATEDIF.
Definition: Difference.php:24
static getDateValue($dateValue, bool $allowBool=true)
getDateValue.
Definition: Helpers.php:31
static flattenArray($array)
Convert a multi-dimensional array to a simple 1-dimensional array.
Definition: Functions.php:583
static flattenSingleValue($value='')
Convert an array to a single scalar value by extracting the first element.
Definition: Functions.php:649
$i
Definition: disco.tpl.php:19
$values