function toPrecision (value, precision, options) {
  if (isNaN(value) || !isFinite(value)) {
    return String(value)
  }

  // determine lower and upper bound for exponential notation.
  const lowerExp = _toNumberOrDefault(options?.lowerExp, -3)
  const upperExp = _toNumberOrDefault(options?.upperExp, 5)

  const split = splitNumber(value)
  const rounded = precision ? roundDigits(split, precision) : split
  if (rounded.exponent < lowerExp || rounded.exponent >= upperExp) {
    // exponential notation
    return toExponential(value, precision)
  } else {
    let c = rounded.coefficients
    const e = rounded.exponent

    // append trailing zeros
    if (c.length < precision) {
      c = c.concat(zeros(precision - c.length))
    }

    // append trailing zeros
    // TODO: simplify the next statement
    c = c.concat(zeros(e - c.length + 1 +
      (c.length < precision ? precision - c.length : 0)))

    // prepend zeros
    c = zeros(-e).concat(c)

    const dot = e > 0 ? e : 0
    if (dot < c.length - 1) {
      c.splice(dot + 1, 0, '.')
    }

    return rounded.sign + c.join('')
  }
}
Format a number with a certain precision
@param {number | string} value
@param {number} [precision=undefined] Optional number of digits.
@param {{lowerExp: number | undefined, upperExp: number | undefined}} [options]
                                      By default:
                                        lowerExp = -3 (incl)
                                        upper = +5 (excl)
@return {string}