'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

/**
 * @copyright 2013 Sonia Keys
 * @copyright 2016 commenthol
 * @license MIT
 * @module iterate
 */
/**
 * Iterate: Chapter 5, Iteration.
 *
 * This package is best considered illustrative.  While the functions are
 * usable, they are minimal in showing the points of the chapter text.  More
 * robust functions would handle more cases of overflow, loss of precision,
 * and divergence.
 */

/**
 * decimalPlaces iterates to a fixed number of decimal places.
 *
 * Inputs are an improvement function, a starting value, the number of
 * decimal places desired in the result, and an iteration limit.
 *
 * @throws Error
 * @param {Function} better
 * @param {Number} start - (float)
 * @param {Number} places - (int)
 * @param {Number} maxIterations - (int)
 * @returns {Number}
 */
function decimalPlaces (better, start, places, maxIterations) {
  const d = Math.pow(10, -places);
  for (let i = 0; i < maxIterations; i++) {
    const n = better(start);
    if (Math.abs(n - start) < d) {
      return n
    }
    start = n;
  }
  throw new Error('Maximum iterations reached')
}

/**
 * fullPrecison iterates to (nearly) the full precision of a float64.
 *
 * To allow for a little bit of floating point jitter, FullPrecision iterates
 * to 15 significant figures, which is the maximum number of full significant
 * figures representable in a float64, but still a couple of bits shy of the
 * full representable precision.
 *
 * @throws Error
 * @param {Function} better
 * @param {Number} start - (float)
 * @param {Number} maxIterations - (int)
 * @returns {Number}
 */
function fullPrecision (better, start, maxIterations) {
  for (let i = 0; i < maxIterations; i++) {
    const n = better(start);
    if (Math.abs((n - start) / n) < 1e-15) {
      return n
    }
    start = n;
  }
  throw new Error('Maximum iterations reached')
}

/**
 * binaryRoot finds a root between given bounds by binary search.
 *
 * Inputs are a function on x and the bounds on x.  A root must exist between
 * the given bounds, otherwise the result is not meaningful.
 *
 * @param {Function} f - root function
 * @param {Number} lower - (float)
 * @param {Number} upper - (float)
 * @returns {Number}
 */
function binaryRoot (f, lower, upper) {
  let yLower = f(lower);
  let mid = 0;
  for (let j = 0; j < 52; j++) {
    mid = (lower + upper) / 2;
    const yMid = f(mid);
    if (yMid === 0) {
      break
    }
    if (signbit(yLower) === signbit(yMid)) {
      lower = mid;
      yLower = yMid;
    } else {
      upper = mid;
    }
  }
  return mid
}

function signbit (v) {
  return (v < 0)
}

var iterate = {
  decimalPlaces,
  fullPrecision,
  binaryRoot
};

exports.binaryRoot = binaryRoot;
exports.decimalPlaces = decimalPlaces;
exports.default = iterate;
exports.fullPrecision = fullPrecision;
