spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← All files
name: src/Formulas/Financial.ts
-rw-r--r--
35663
  1import {
  2  ArgsChecker
  3} from "../Utilities/ArgsChecker";
  4import {
  5  TypeConverter,
  6  checkForDevideByZero
  7} from "../Utilities/TypeConverter";
  8import {
  9  NumError,
 10  DivZeroError, ValueError
 11} from "../Errors"
 12import {
 13  YEARFRAC
 14} from "./Date";
 15import {Filter} from "../Utilities/Filter";
 16import {isDefined, isUndefined} from "../Utilities/MoreUtils";
 17
 18
 19/**
 20 * Calculates the depreciation of an asset for a specified period using the double-declining balance method.
 21 * @param cost - The initial cost of the asset.
 22 * @param salvage - The value of the asset at the end of depreciation.
 23 * @param life - The number of periods over which the asset is depreciated.
 24 * @param period - The single period within life for which to calculate depreciation.
 25 * @param factor - [ OPTIONAL - 2 by default ] - The factor by which depreciation decreases.
 26 * @returns {number} depreciation of an asset for a specified period
 27 * @constructor
 28 */
 29let DDB = function (cost, salvage, life, period, factor?) : number {
 30  ArgsChecker.checkLengthWithin(arguments, 4, 5, "DDB");
 31  cost = TypeConverter.firstValueAsNumber(cost);
 32  salvage = TypeConverter.firstValueAsNumber(salvage);
 33  life = TypeConverter.firstValueAsNumber(life);
 34  period = TypeConverter.firstValueAsNumber(period);
 35  factor = factor === undefined ? 2 : TypeConverter.firstValueAsNumber(factor);
 36
 37  if (cost < 0) {
 38    throw new NumError("Function DDB parameter 1 value is "
 39      + cost + ". It should be greater than or equal to 0.");
 40  }
 41  if (salvage < 0) {
 42    throw new NumError("Function DDB parameter 2 value is "
 43      + salvage + ". It should be greater than or equal to 0.");
 44  }
 45  if (life < 0) {
 46    throw new NumError("Function DDB parameter 3 value is "
 47      + life + ". It should be greater than or equal to 0.");
 48  }
 49  if (period < 0) {
 50    throw new NumError("Function DDB parameter 4 value is "
 51      + period + ". It should be greater than or equal to 0.");
 52  }
 53  if (period > life) {
 54    throw new NumError("Function DDB parameter 4 value is "
 55      + life + ". It should be less than or equal to value of Function DB parameter 3 with "+ period +".");
 56  }
 57  if (salvage >= cost) {
 58    return 0;
 59  }
 60
 61  let total = 0;
 62  let current = 0;
 63  for (let i = 1; i <= period; i++) {
 64    current = Math.min((cost - total) * (factor / checkForDevideByZero(life)), (cost - salvage - total));
 65    total += current;
 66  }
 67  return current;
 68};
 69
 70
 71/**
 72 * Calculates the depreciation of an asset for a specified period using the arithmetic declining balance method.
 73 * @param cost - The initial cost of the asset.
 74 * @param salvage - The value of the asset at the end of depreciation.
 75 * @param life - The number of periods over which the asset is depreciated.
 76 * @param period - The single period within life for which to calculate depreciation.
 77 * @param month - [ OPTIONAL - 12 by default ] - The number of months in the first year of depreciation.
 78 * @returns {number} depreciated value
 79 * @constructor
 80 */
 81let DB = function (cost, salvage, life, period, month) : number {
 82  ArgsChecker.checkLengthWithin(arguments, 4, 5, "DB");
 83  cost = TypeConverter.firstValueAsNumber(cost);
 84  salvage = TypeConverter.firstValueAsNumber(salvage);
 85  life = TypeConverter.firstValueAsNumber(life);
 86  period = TypeConverter.firstValueAsNumber(period);
 87  month = month !== undefined ? Math.floor(TypeConverter.firstValueAsNumber(month)) : 12;
 88  if (cost < 0) {
 89    throw new NumError("Function DB parameter 1 value is "
 90      + cost + ". It should be greater than or equal to 0.");
 91  }
 92  if (salvage < 0) {
 93    throw new NumError("Function DB parameter 2 value is "
 94      + salvage + ". It should be greater than or equal to 0.");
 95  }
 96  if (life < 0) {
 97    throw new NumError("Function DB parameter 3 value is "
 98      + life + ". It should be greater than or equal to 0.");
 99  }
100  if (period < 0) {
101    throw new NumError("Function DB parameter 4 value is "
102      + period + ". It should be greater than or equal to 0.");
103  }
104  if (month > 12 || month < 1) {
105    throw new NumError("Function DB parameter 5 value is "
106      + month + ". Valid values are between 1 and 12 inclusive.");
107  }
108  if (period > life) {
109    throw new NumError("Function DB parameter 4 value is "
110      + life + ". It should be less than or equal to value of Function DB parameter 3 with "+ period +".");
111  }
112  if (salvage >= cost) {
113    return 0;
114  }
115  if (cost === 0 && salvage !== 0) {
116    throw new DivZeroError("Evaluation of function DB cause a divide by zero error.")
117  }
118  let rate = (1 - Math.pow(salvage / cost, 1 / life));
119  let initial = cost * rate * month / 12;
120  let total = initial;
121  let current = 0;
122  let ceiling = (period === life) ? life - 1 : period;
123  for (let i = 2; i <= ceiling; i++) {
124    current = (cost - total) * rate;
125    total += current;
126  }
127  if (period === 1) {
128    return initial;
129  } else if (period === life) {
130    return (cost - total) * rate;
131  } else {
132    return current;
133  }
134};
135
136/**
137 * Formats a number into the locale-specific currency format. WARNING: Currently the equivalent of TRUNC, since this
138 * returns numbers
139 * @param number - The value to be formatted.
140 * @param places - [ OPTIONAL - 2 by default ] - The number of decimal places to display.
141 * @returns {number} dollars
142 * @constructor
143 */
144let DOLLAR = function (number, places?) : number {
145  ArgsChecker.checkLengthWithin(arguments, 1, 2, "DOLLAR");
146  let v = TypeConverter.firstValueAsNumber(number);
147  places = places !== undefined ? TypeConverter.firstValueAsNumber(places) : 2;
148  let sign = (v > 0) ? 1 : -1;
149  let divisor = sign * (Math.floor(Math.abs(v) * Math.pow(10, places)));
150  let pow = Math.pow(10, places);
151  if (pow === 0 && divisor !== 0) {
152    throw new DivZeroError("Evaluation of function DOLLAR cause a divide by zero error.")
153  }
154  return divisor / pow;
155};
156
157
158/**
159 * Converts a price quotation given as a decimal fraction into a decimal value.
160 * @param fractionalPrice - The price quotation given using fractional decimal conventions.
161 * @param unit - The units of the fraction, e.g. 8 for 1/8ths or 32 for 1/32nds.
162 * @returns {number} decimal value.
163 * @constructor
164 */
165let DOLLARDE = function (fractionalPrice, unit) : number {
166  ArgsChecker.checkLength(arguments, 2, "DOLLARDE");
167  let dollar = TypeConverter.firstValueAsNumber(fractionalPrice);
168  let fraction = Math.floor(TypeConverter.firstValueAsNumber(unit));
169  if (fraction === 0) {
170    throw new DivZeroError("Function DOLLARDE parameter 2 cannot be zero.");
171  }
172  let result = parseInt(dollar.toString(), 10);
173  result += (dollar % 1) * Math.pow(10, Math.ceil(Math.log(fraction) / Math.LN10)) / fraction;
174  let power = Math.pow(10, Math.ceil(Math.log(fraction) / Math.LN2) + 1);
175  if (power === 0) {
176    throw new DivZeroError("Evaluation of function DOLLARDE cause a divide by zero error.")
177  }
178  result = Math.round(result * power) / power;
179  return result;
180};
181
182
183/**
184 * Converts a price quotation given as a decimal value into a decimal fraction.
185 * @param decimalPrice - The price quotation given as a decimal value.
186 * @param unit - The units of the desired fraction, e.g. 8 for 1/8ths or 32 for 1/32nds
187 * @returns {number} price quotation as decimal fraction.
188 * @constructor
189 */
190let DOLLARFR = function (decimalPrice, unit) : number {
191  ArgsChecker.checkLength(arguments, 2, "DOLLARFR");
192  decimalPrice = TypeConverter.firstValueAsNumber(decimalPrice);
193  unit = Math.floor(TypeConverter.firstValueAsNumber(unit));
194  if (unit === 0) {
195    throw new DivZeroError("Function DOLLARFR parameter 2 cannot be zero.");
196  }
197  let result = parseInt(decimalPrice.toString(), 10);
198  result += (decimalPrice % 1) * Math.pow(10, -Math.ceil(Math.log(unit) / Math.LN10)) * unit;
199  return result;
200};
201
202
203/**
204 * Calculates the annual effective interest rate given the nominal rate and number of compounding periods per year.
205 * @param nominalRate - The nominal interest rate per year.
206 * @param periodsPerYear - The number of compounding periods per year.
207 * @returns {number} annual effective interest rate
208 * @constructor
209 */
210let EFFECT = function (nominalRate, periodsPerYear) : number {
211  ArgsChecker.checkLength(arguments, 2, "EFFECT");
212  let rate = TypeConverter.firstValueAsNumber(nominalRate);
213  let periods = TypeConverter.firstValueAsNumber(periodsPerYear);
214  if (rate <= 0) {
215    throw new NumError("Function EFFECT parameter 1 value is " + rate + ". It should be greater than to 0");
216  }
217  if (periods < 1) {
218    throw new NumError("Function EFFECT parameter 2 value is " + periods + ". It should be greater than or equal to 1");
219  }
220  periods = Math.floor(periods);
221  return Math.pow(1 + rate / periods, periods) - 1;
222};
223
224
225/**
226 * Calculates the periodic payment for an annuity investment based on constant-amount periodic payments and a constant
227 * interest rate.
228 * @param rate - The interest rate.
229 * @param periods - The number of payments to be made.
230 * @param presentValue - The current value of the annuity.
231 * @param futureValue [ OPTIONAL ] - The future value remaining after the final payment has been made.
232 * @param endOrBeginning [ OPTIONAL - 0 by default ] - Whether payments are due at the end (0) or beginning (1) of each
233 * period.
234 * @returns {number}
235 * @constructor
236 */
237let PMT = function (rate, periods, presentValue, futureValue?, endOrBeginning?) : number {
238  ArgsChecker.checkLengthWithin(arguments, 3, 5, "PMT");
239  rate = TypeConverter.firstValueAsNumber(rate);
240  periods = TypeConverter.firstValueAsNumber(periods);
241  presentValue = TypeConverter.firstValueAsNumber(presentValue);
242  futureValue = futureValue ? TypeConverter.firstValueAsNumber(futureValue) : 0;
243  endOrBeginning = endOrBeginning ? TypeConverter.firstValueAsNumber(endOrBeginning) : 0;
244  let result;
245  if (rate === 0) {
246    result = (presentValue + futureValue) / periods;
247  } else {
248    let term = Math.pow(1 + rate, periods);
249    if (endOrBeginning) {
250      result = (futureValue * rate / (term - 1) + presentValue * rate / (1 - 1 / term)) / (1 + rate);
251    } else {
252      result = futureValue * rate / (term - 1) + presentValue * rate / (1 - 1 / term);
253    }
254  }
255  return -result;
256};
257
258
259/**
260 * Returns the future value of an investment based on periodic, constant payments and a constant interest rate.
261 * @param rate - The rate of periodic interest.
262 * @param periods - The total number of periods.
263 * @param payment - The annuity paid regularly per period
264 * @param value - [OPTIONAL] - The present cash value of an investment.
265 * @param type - [OPTIONAL] - Defines whether the payment is due at the beginning (1) or the end (0) of a period.
266 * @returns {number}
267 * @constructor
268 */
269let FV = function (rate, periods, payment, value?, type?) {
270  ArgsChecker.checkLengthWithin(arguments, 3, 5, "FV");
271  rate = TypeConverter.firstValueAsNumber(rate);
272  periods = TypeConverter.firstValueAsNumber(periods);
273  payment = TypeConverter.firstValueAsNumber(payment);
274  value = (typeof value === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(value);
275  type = (typeof type === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(type);
276  let result;
277  if (rate === 0) {
278    result = value + payment * periods;
279  } else {
280    let term = Math.pow(1 + rate, periods);
281    if (type === 0) {
282      result = value * term + payment * (term - 1) / rate;
283    } else {
284      result = value * term + payment * (1 + rate) * (term - 1.0) / rate;
285    }
286  }
287  return -result;
288};
289
290/**
291 * Calculates the cumulative principal paid over a range of payment periods for an investment based on constant-amount
292 * periodic payments and a constant interest rate.
293 * @param rate - The interest rate.
294 * @param numberOfPeriods - The number of payments to be made.
295 * @param presentValue - The current value of the annuity.
296 * @param firstPeriod - The number of the payment period to begin the cumulative calculation. must be greater
297 * than or equal to 1.
298 * @param lastPeriod - The number of the payment period to end the cumulative calculation, must be greater
299 * than first_period.
300 * @param endOrBeginning - Whether payments are due at the end (0) or beginning (1) of each period
301 * @returns {number} cumulative principal
302 * @constructor
303 */
304let CUMPRINC = function (rate, numberOfPeriods, presentValue, firstPeriod, lastPeriod, endOrBeginning) : number {
305  ArgsChecker.checkLength(arguments, 6, "CUMPRINC");
306  rate = TypeConverter.firstValueAsNumber(rate);
307  let periods = TypeConverter.firstValueAsNumber(numberOfPeriods);
308  let value = TypeConverter.firstValueAsNumber(presentValue);
309  let start = TypeConverter.firstValueAsNumber(firstPeriod);
310  if (start < 1) {
311    throw new NumError("Function CUMPRINC parameter 4 value is " + start + ". It should be greater than or equal to 1.");
312  }
313  let end = TypeConverter.firstValueAsNumber(lastPeriod);
314  if (end < 1) {
315    throw new NumError("Function CUMPRINC parameter 5 value is " + end + ". It should be greater than or equal to 1.");
316  }
317  if (end < start) {
318    throw new NumError("Function CUMPRINC parameter 5 value is " + end + ". It should be greater than or equal to " + start + ".");
319  }
320  let type = TypeConverter.firstValueAsBoolean(endOrBeginning);
321
322  let payment = PMT(rate, periods, value, 0, type);
323  let principal = 0;
324  if (start === 1) {
325    if (type) {
326      principal = payment;
327    } else {
328      principal = payment + value * rate;
329    }
330    start++;
331  }
332  for (let i = start; i <= end; i++) {
333    if (type) {
334      principal += payment - (FV(rate, i - 2, payment, value, 1) - payment) * rate;
335    } else {
336      principal += payment - FV(rate, i - 1, payment, value, 0) * rate;
337    }
338  }
339  return principal;
340};
341
342/**
343 * Calculates the cumulative interest over a range of payment periods for an investment based on constant-amount
344 * periodic payments and a constant interest rate.
345 * @param rate - The interest rate.
346 * @param numberOfPeriods - The number of payments to be made.
347 * @param presentValue - The current value of the annuity.
348 * @param firstPeriod - The number of the payment period to begin the cumulative calculation, must be greater
349 * than or equal to 1.
350 * @param lastPeriod - The number of the payment period to end the cumulative calculation, must be greater
351 * than first_period.
352 * @param endOrBeginning - Whether payments are due at the end (0) or beginning (1) of each period.
353 * @returns {number} cumulative interest
354 * @constructor
355 */
356let CUMIPMT = function (rate, numberOfPeriods, presentValue, firstPeriod, lastPeriod, endOrBeginning) : number {
357  ArgsChecker.checkLength(arguments, 6, "CUMIPMT");
358  rate = TypeConverter.firstValueAsNumber(rate);
359  let periods = TypeConverter.firstValueAsNumber(numberOfPeriods);
360  let value = TypeConverter.firstValueAsNumber(presentValue);
361  let start = TypeConverter.firstValueAsNumber(firstPeriod);
362  if (start < 1) {
363    throw new NumError("Function CUMPRINC parameter 4 value is " + start + ". It should be greater than or equal to 1.");
364  }
365  let end = TypeConverter.firstValueAsNumber(lastPeriod);
366  if (end < 1) {
367    throw new NumError("Function CUMPRINC parameter 5 value is " + end + ". It should be greater than or equal to 1.");
368  }
369  if (end < start) {
370    throw new NumError("Function CUMPRINC parameter 5 value is " + end + ". It should be greater than or equal to " + start + ".");
371  }
372  let type = TypeConverter.firstValueAsBoolean(endOrBeginning);
373
374  let payment = PMT(rate, periods, value, 0, type);
375  let interest = 0;
376  if (start === 1) {
377    if (!type) {
378      interest = -value;
379      start++;
380    } else {
381      start++;
382    }
383  }
384  for (let i = start; i <= end; i++) {
385    if (type) {
386      interest += FV(rate, i - 2, payment, value, 1) - payment;
387    } else {
388      interest += FV(rate, i - 1, payment, value, 0);
389    }
390  }
391  interest *= rate;
392  return interest;
393};
394
395/**
396 * Calculates the accrued interest of a security that has periodic payments.
397 * WARNING: This function has been implemented to specifications as outlined in Google Spreadsheets, LibreOffice, and
398 * OpenOffice. It functions much the same as MSExcel's ACCRINT, but there are several key differences. Below are links
399 * to illustrate the differences. Please see the source code for more information on differences. Links: https://quant.stackexchange.com/questions/7040/whats-the-algorithm-behind-excels-accrint, https://support.office.com/en-us/article/ACCRINT-function-fe45d089-6722-4fb3-9379-e1f911d8dc74, https://quant.stackexchange.com/questions/7040/whats-the-algorithm-behind-excels-accrint, https://support.google.com/docs/answer/3093200 .
400 * @param issue - The date the security was initially issued.
401 * @param firstPayment - The first date interest will be paid.
402 * @param settlement - The settlement date of the security, the date after issuance when the security is
403 * delivered to the buyer. Is the maturity date of the security if it is held until maturity rather than sold.
404 * @param rate - The annualized rate of interest.
405 * @param redemption - The redemption amount per 100 face value, or par.
406 * @param frequency - The number of coupon payments per year. For annual payments, frequency = 1; for
407 * semiannual, frequency = 2; for quarterly, frequency = 4.
408 * @param dayCountConvention - [ OPTIONAL - 0 by default ] - An indicator of what day count method to use.
409 * 0 or omitted = US (NASD) 30/360, 1 = Actual/actual, 2 = Actual/360, 3 = Actual/365, 4 = European 30/360.
410 * @returns {number}
411 * @constructor
412 * TODO: This function is based off of the open-source versions I was able to dig up online. We should implement a
413 * TODO:     second version that is closer to what MSExcel does and is named something like `ACCRINT.MS`.
414 */
415let ACCRINT = function (issue, firstPayment, settlement, rate, redemption, frequency, dayCountConvention?) {
416  ArgsChecker.checkLengthWithin(arguments, 6, 7, "ACCRINT");
417  issue = TypeConverter.firstValueAsDateNumber(issue);
418  // "firstPayment" param is only here to check for errors for GS implementation.
419  // In MSE, there is a 7th (zero-indexed-6th) param that indicates the calculation-method to use, which indicates
420  // weather the total accrued interest starting at the first_intrest date, instead of the issue date.
421  firstPayment = TypeConverter.firstValueAsDateNumber(firstPayment);
422  if (firstPayment < 0) {
423    throw new NumError("Function ACCRINT parameter 2 value is " + firstPayment
424        + ". It should be greater than 0.");
425  }
426  settlement = TypeConverter.firstValueAsDateNumber(settlement);
427  if (issue > settlement) {
428    throw new NumError("Function ACCRINT parameter 1 (" + issue.toString()
429      + ") should be on or before Function ACCRINT parameter 3 (" + settlement.toString() + ").")
430  }
431  rate = TypeConverter.firstValueAsNumber(rate);
432  // redemption, aka "par"
433  redemption = TypeConverter.firstValueAsNumber(redemption);
434  // The frequency parameter also does not affect the resulting value of the formula in the GS implementation.
435  // In MSE, frequency is used to calculate a more accurate value, by breaking apart the year, and computing interest
436  // on an on-going basis. In this implementation, we use YEARFRAC to get a numerical value that encompasses the
437  // functionality of "frequency".
438  frequency = TypeConverter.firstValueAsNumber(frequency);
439  // dayCountConvention, aka "basis"
440  dayCountConvention = dayCountConvention !== undefined ? TypeConverter.firstValueAsNumber(dayCountConvention) : 1;
441  let factor = YEARFRAC(issue, settlement, dayCountConvention);
442  return redemption * rate * factor;
443};
444
445
446/**
447 * Returns the arithmetic-declining depreciation rate. Use this function to calculate the depreciation amount for one
448 * period of the total depreciation span of an object. Arithmetic declining depreciation reduces the depreciation amount
449 * from period to period by a fixed sum.
450 * @param cost - The initial cost of an asset.
451 * @param salvage - the value of an asset after depreciation.
452 * @param life - The period fixing the time span over which an asset is depreciated.
453 * @param period - The period for which the depreciation is to be calculated.
454 * @returns {number}
455 * @constructor
456 */
457let SYD = function (cost, salvage, life, period) {
458  ArgsChecker.checkLength(arguments, 4, "SYD");
459  cost = TypeConverter.firstValueAsNumber(cost);
460  salvage = TypeConverter.firstValueAsNumber(salvage);
461  life = TypeConverter.firstValueAsNumber(life);
462  period = TypeConverter.firstValueAsNumber(period);
463  // Return error if period is lower than 1 or greater than life
464  if (period > life) {
465    throw new NumError("Function SYD parameter 4 value is " + period +
466        ". It should be less than or equal to value of Function SYD parameter 3 with " + life + ".");
467  }
468  if (period < 1) {
469    throw new NumError("Function SYD parameter 4 value is " + period + ". It should be greater than 0.");
470  }
471  period = Math.floor(period);
472
473  return (cost - salvage) * (life - period + 1) * 2 / (life * (life + 1));
474};
475
476
477/**
478 * Returns the straight-line depreciation of an asset for one period. The amount of the depreciation is constant during
479 * the depreciation period.
480 * @param cost - The initial cost of the asset.
481 * @param salvage - The value of an asset at the end of the depreciation.
482 * @param life - The depreciation period determining the number of periods in the deprecation of the asset.
483 * @returns {number}
484 * @constructor
485 */
486let SLN = function (cost, salvage, life) {
487  ArgsChecker.checkLength(arguments, 3, "SYD");
488  cost = TypeConverter.firstValueAsNumber(cost);
489  salvage = TypeConverter.firstValueAsNumber(salvage);
490  life = TypeConverter.firstValueAsNumber(life);
491  if (life === 0) {
492    throw new DivZeroError("Function SLN parameter 3 cannot be zero.");
493  }
494  return (cost - salvage) / life;
495};
496
497
498/**
499 * Returns the net present value of an investment based on a series of periodic cash flows and a discount rate.
500 * @param rate - The discount rate for a period.
501 * @param values - The values representing deposits or withdrawals.
502 * @returns {number}
503 * @constructor
504 * TODO: This function can return results that are prone to floating point precision errors.
505 */
506let NPV = function (rate, ...values) {
507  ArgsChecker.checkAtLeastLength(arguments, 2, "NPV");
508  let range = Filter.flattenAndThrow(values).map(function (value) {
509    try {
510      return TypeConverter.valueToNumber(value);
511    } catch (e) {
512      throw new ValueError("Function NPV expects number values. But '" + value + "' is " + (typeof value)
513          + " and cannot be coerced to a number.")
514    }
515  });
516  let value = 0;
517  for (let i = 0; i < range.length; i++) {
518    value += range[i] / Math.pow(1 + rate, i);
519  }
520  return value;
521};
522
523/**
524 * Returns the number of payment for an investment. Number is based on constant-amount payments made periodically and a
525 * constant interest rate.
526 * @param rate - The interest rate.
527 * @param payment - The amount of each payment.
528 * @param present - THe current value.
529 * @param future - [OPTIONAL] - The future value remaining after the final payment has been made.
530 * @param type [OPTIONAL 0 by default] - 1 indicates payments are due at the beginning of each period. 0 indicates
531 * payments are due at the end of each period.
532 * @returns {number}
533 * @constructor
534 */
535let NPER = function (rate, payment, present, future?, type?) {
536  ArgsChecker.checkLengthWithin(arguments, 3, 5, "NPER");
537  rate = TypeConverter.firstValueAsNumber(rate);
538  payment = TypeConverter.firstValueAsNumber(payment);
539  present = TypeConverter.firstValueAsNumber(present);
540  type = (typeof type === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(type);
541  future = (typeof future === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(future);
542  let num = payment * (1 + rate * type) - future * rate;
543  let den = (present * rate + payment * (1 + rate * type));
544  if (den === 0) {
545    throw new DivZeroError("Evaluation of function NPER cause a divide by zero error.");
546  }
547  let div = Math.log(1 + rate);
548  let logNumDen = Math.log(num / den);
549  if (isNaN(logNumDen)) {
550    throw new NumError("Parameters given function NPER are not possible.");
551  }
552  return  logNumDen / div;
553};
554
555/**
556 * Calculates the yearly nominal interest rate, given the effective rate and the number of compounding periods per year.
557 * @param rate - The effective interest rate.
558 * @param periods - The number of periodic interest payments per year.
559 * @returns {number}
560 * @constructor
561 */
562let NOMINAL =  function (rate, periods) {
563  ArgsChecker.checkLength(arguments, 2, "NOMINAL");
564  rate = TypeConverter.firstValueAsNumber(rate);
565  periods = Math.round(TypeConverter.firstValueAsNumber(periods));
566  if (periods < 1) {
567    throw new NumError("Function NOMINAL parameter 2 value is " + periods
568        + ". It should be greater than or equal to 1.");
569  }
570  return (Math.pow(rate + 1, 1 / periods) - 1) * periods;
571};
572
573
574/**
575 * Calculates the modified internal rate of return of a series of investments.
576 * @param values - Range or values of payments. Ignores text values.
577 * @param financeRate - The rate of interest of the investments.
578 * @param reinvestRate - The rate of interest of the reinvestment.
579 * @returns {number}
580 * @constructor
581 * TODO: This relies on NPV and will therefore be prone to floating-point errors.
582 */
583let MIRR = function (values, financeRate, reinvestRate) {
584  ArgsChecker.checkLength(arguments, 3, "MIRR");
585  values = Filter.flattenAndThrow(values).filter(function (value) {
586    return (typeof value !== "string");
587  }).map(function (value) {
588    return TypeConverter.valueToNumber(value);
589  });
590  let n = values.length;
591
592  let payments = [];
593  let incomes = [];
594  for (let i = 0; i < n; i++) {
595    if (values[i] < 0) {
596      payments.push(values[i]);
597    } else {
598      incomes.push(values[i]);
599    }
600  }
601  if (incomes.length === 0 || payments.length === 0) {
602    throw new DivZeroError("For MIRR, the values must include positive and negative numbers.");
603  }
604
605  let num = -NPV(reinvestRate, incomes) * Math.pow(1 + reinvestRate, n - 1);
606  let den = NPV(financeRate, payments) * (1 + financeRate);
607  return Math.pow(num / den, 1 / (n - 1)) - 1;
608};
609
610
611/**
612 * Calculates the internal rate of return for an investment. The values represent cash flow values at regular intervals;
613 * at least one value must be negative (payments), and at least one value must be positive (income).
614 *
615 * Relevant StackOverflow discussion: https://stackoverflow.com/questions/15089151/javascript-irr-internal-rate-of-return-formula-accuracy
616 *
617 * @param values - Range containing values. Ignores text values.
618 * @param guess - [OPTIONAL] - The estimated value. Defaults to 0.01.
619 * @returns {number}
620 * @constructor
621 */
622let IRR =  function (values, guess?) {
623  ArgsChecker.checkLengthWithin(arguments, 1, 2, "IRR");
624  values = Filter.flattenAndThrow(values).filter(function (value) {
625    return (typeof value !== "string");
626  }).map(function (value) {
627    return TypeConverter.valueToNumber(value);
628  });
629  guess = (guess === undefined) ? 0.1 : TypeConverter.firstValueAsNumber(guess);
630  let min = -1.0;
631  let max = 10.0;
632  let val;
633  let counter = 1;
634  const MAX_ITERATIONS = 500000;
635  do {
636    guess = (min + max) / 2;
637    val = 0;
638    for (let j = 0; j < values.length; j++) {
639      val += values[j] / Math.pow((1 + guess), j);
640    }
641    if (val > 0) {
642      min = guess;
643    }
644    else {
645      max = guess;
646    }
647  } while(Math.abs(val) > 0.000001 && ++counter < MAX_ITERATIONS);
648  return guess;
649};
650
651
652/**
653 * Calculates the periodic amortization for an investment with regular payments and a constant interest rate.
654 * @param rate - The periodic interest rate.
655 * @param period - The period for which the compound interest is calculated.
656 * @param periods - The total number of periods during which the annuity is paid.
657 * @param present - The present cash value in sequence of payments.
658 * @param future - [OPTIONAL] - The desired value (future value) at the end of the periods.
659 * @param type - [OPTIONAL] - Defines whether the payment is due at the beginning (1) or the end (0) of a period.
660 * @returns {number}
661 * @constructor
662 */
663let IPMT = function (rate, period, periods, present, future?, type?) {
664  ArgsChecker.checkLengthWithin(arguments, 4, 6, "IPMT");
665  rate = TypeConverter.firstValueAsNumber(rate);
666  period = TypeConverter.firstValueAsNumber(period);
667  periods = TypeConverter.firstValueAsNumber(periods);
668  present = TypeConverter.firstValueAsNumber(present);
669  future = (typeof future === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(future);
670  type = (typeof type === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(type);
671  let payment = PMT(rate, periods, present, future, type);
672  let interest;
673  if (period === 1) {
674    if (type === 1) {
675      interest = 0;
676    } else {
677      interest = -present;
678    }
679  } else {
680    if (type === 1) {
681      interest = FV(rate, period - 2, payment, present, 1) - payment;
682    } else {
683      interest = FV(rate, period - 1, payment, present, 0);
684    }
685  }
686  return interest * rate;
687};
688
689
690/**
691 * Returns for a given period the payment on the principal for an investment that is based on periodic and constant
692 * payments and a constant interest rate.
693 * @param rate - The periodic interest rate.
694 * @param period - The amortization period.
695 * @param periods - The total number of periods during which the annuity is paid.
696 * @param present - The present value in the sequence of payments.
697 * @param future - [OPTIONAL] - The desired future value. Defaults to 0.
698 * @param type - [OPTIONAL] - Indicates how the year is to be calculated. 0 indicates payments are due at end of
699 * period, 1 indicates payments are due at beginning of period. Defaults to 0.
700 * @returns {number}
701 * @constructor
702 */
703let PPMT = function (rate, period, periods, present, future?, type?) {
704  ArgsChecker.checkLengthWithin(arguments, 4, 6, "PPMT");
705  rate = TypeConverter.firstValueAsNumber(rate);
706  period = TypeConverter.firstValueAsNumber(period);
707  if (period < 1) {
708    throw new NumError("Function PPMT parameter 2 value is " + period + ", but should be greater than or equal to 1.");
709  }
710  periods = TypeConverter.firstValueAsNumber(periods);
711  if (periods <= 0) {
712    throw new NumError("Function PPMT parameter 3 value is " + periods + ", but should be greater than 0.");
713  }
714  present = TypeConverter.firstValueAsNumber(present);
715  future = (typeof future === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(future);
716  type = (typeof type === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(type);
717  return PMT(rate, periods, present, future, type) - IPMT(rate, period, periods, present, future, type);
718};
719
720
721/**
722 * Calculates the accumulated value of the starting capital for a series of periodically varying interest rates.
723 * @param principal - The starting capital.
724 * @param rateSchedule - Range or Array that is a series of interest rates.
725 * @returns {number}
726 * @constructor
727 */
728let FVSCHEDULE =  function (principal, rateSchedule) {
729  ArgsChecker.checkLength(arguments, 2, "FVSCHEDULE");
730  let future = TypeConverter.firstValueAsNumber(principal);
731  rateSchedule = Filter.flattenAndThrow(rateSchedule);
732
733  for (let i = 0; i < rateSchedule.length; i++) {
734    // Apply scheduled interest
735    future *= 1 + rateSchedule[i];
736  }
737  return future;
738};
739
740
741/**
742 * Returns the present value of an investment resulting from a series of regular payments.
743 * @param rate - The interest rate per period.
744 * @param periods - The total number of payment periods
745 * @param paymentPerPeriod - The regular payment made per period.
746 * @param future - [OPTIONAL defaults to 0] The future value remaining after the final installment has been made
747 * @param type - [OPTIONAL defaults to 0] Defines whether the payment is due at the beginning (1) or the end (0) of a
748 * period.
749 * @constructor
750 */
751let PV = function (rate, periods, paymentPerPeriod, future?, type?) {
752  ArgsChecker.checkLengthWithin(arguments, 3, 5, "PV");
753  rate = TypeConverter.firstValueAsNumber(rate);
754  if (rate < 0) {
755    throw new NumError("Function PV parameter 21value is " + rate + ", but should be greater than or equal to 0.");
756  }
757  periods = TypeConverter.firstValueAsNumber(periods);
758  paymentPerPeriod = TypeConverter.firstValueAsNumber(paymentPerPeriod);
759  future = isUndefined(future) ? 0 : TypeConverter.firstValueAsNumber(future);
760  type = isUndefined(type) ? 0 : TypeConverter.firstValueAsNumber(type);
761  if (rate === 0) {
762    return -paymentPerPeriod * periods - future;
763  } else {
764    return (((1 - Math.pow(1 + rate, periods)) / rate) * paymentPerPeriod * (1 + rate * type) - future) / Math.pow(1 + rate, periods);
765  }
766};
767
768
769/**
770 * Returns the constant interest rate per period of an annuity.
771 * @param periods - The total number of periods, during which payments are made (payment period).
772 * @param paymentPerPeriod - The constant payment (annuity) paid during each period.
773 * @param presentValue - The cash value in the sequence of payments
774 * @param futureValue - [OPTIONAL defaults to 0] The future value, which is reached at the end of the periodic payments.
775 * @param beginningOrEnd - [OPTIONAL defaults to 0] Defines whether the payment is due at the beginning (1) or the end
776 * (0) of a period.
777 * @param guessRate - [OPTIONAL] - Determines the estimated value of the interest with iterative
778 * calculation.
779 * @constructor
780 */
781let RATE = function (periods, paymentPerPeriod, presentValue, futureValue?, beginningOrEnd?, guessRate?) {
782  ArgsChecker.checkLengthWithin(arguments, 3, 6, "RATE");
783  periods = TypeConverter.firstValueAsNumber(periods);
784  if (periods < 1) {
785    throw new NumError("Function RATE parameter 1 value is" + periods + ", but it should be greater than 0.");
786  }
787  paymentPerPeriod = TypeConverter.firstValueAsNumber(paymentPerPeriod);
788  presentValue = TypeConverter.firstValueAsNumber(presentValue);
789  futureValue = isDefined(futureValue) ? TypeConverter.firstValueAsNumber(futureValue) : 0;
790  beginningOrEnd = isDefined(beginningOrEnd) ? TypeConverter.firstValueAsNumber(beginningOrEnd) : 0;
791  guessRate = isDefined(guessRate) ? TypeConverter.firstValueAsNumber(guessRate) : 0.1;
792
793  // Sets the limits for possible guesses to any
794  // number between 0% and 100%
795  let lowLimit = 0;
796  let highLimit = 1;
797  let guess = guessRate;
798
799  // Defines a tolerance of up to +/- 0.00005% of pmt, to accept
800  // the solution as valid.
801  let tolerance = Math.abs(0.00000005 * paymentPerPeriod);
802
803  // Tries at most 40 times to find a solution within the tolerance.
804  for (let i = 0; i < 40; i++) {
805    // Resets the balance to the original pv.
806    let balance = presentValue;
807
808    // Calculates the balance at the end of the loan, based
809    // on loan conditions.
810    for (let j = 0; j < periods; j++ ) {
811      if (beginningOrEnd == 0) {
812        // Interests applied before payment
813        balance = balance * (1 + guess) + paymentPerPeriod;
814      } else {
815        // Payments applied before insterests
816        balance = (balance + paymentPerPeriod) * (1 + guess);
817      }
818    }
819
820    // Returns the guess if balance is within tolerance.  If not, adjusts
821    // the limits and starts with a new guess.
822    if (Math.abs(balance + futureValue) < tolerance) {
823      return guess;
824    } else if (balance + futureValue > 0) {
825      // Sets a new highLimit knowing that
826      // the current guess was too big.
827      highLimit = guess;
828    } else  {
829      // Sets a new lowLimit knowing that
830      // the current guess was too small.
831      lowLimit = guess;
832    }
833
834    // Calculates the new guess.
835    guess = (highLimit + lowLimit) / 2;
836  }
837  throw new NumError("RATE attempted to complete but it was not able to.");
838};
839
840
841export {
842  ACCRINT,
843  CUMPRINC,
844  CUMIPMT,
845  DB,
846  DDB,
847  DOLLAR,
848  DOLLARDE,
849  DOLLARFR,
850  EFFECT,
851  PMT,
852  SYD,
853  SLN,
854  NPV,
855  NPER,
856  NOMINAL,
857  MIRR,
858  IRR,
859  IPMT,
860  FV,
861  PPMT,
862  FVSCHEDULE,
863  PV,
864  RATE
865}