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 }
static interval($startDate, $endDate, $unit='D')
DATEDIF.
Definition: Difference.php:24
static flattenArray($array)
Convert a multi-dimensional array to a simple 1-dimensional array.
Definition: Functions.php:583
$values
static getDateValue($dateValue, bool $allowBool=true)
getDateValue.
Definition: Helpers.php:31
$i
Definition: disco.tpl.php:19
static flattenSingleValue($value='')
Convert an array to a single scalar value by extracting the first element.
Definition: Functions.php:649