simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

number.js (20473B)


      1 import { isNumber } from './is.js';
      2 /**
      3  * @typedef {{sign: '+' | '-' | '', coefficients: number[], exponent: number}} SplitValue
      4  */
      5 
      6 /**
      7  * Check if a number is integer
      8  * @param {number | boolean} value
      9  * @return {boolean} isInteger
     10  */
     11 
     12 export function isInteger(value) {
     13   if (typeof value === 'boolean') {
     14     return true;
     15   }
     16 
     17   return isFinite(value) ? value === Math.round(value) : false;
     18 }
     19 /**
     20  * Calculate the sign of a number
     21  * @param {number} x
     22  * @returns {number}
     23  */
     24 
     25 export var sign = /* #__PURE__ */Math.sign || function (x) {
     26   if (x > 0) {
     27     return 1;
     28   } else if (x < 0) {
     29     return -1;
     30   } else {
     31     return 0;
     32   }
     33 };
     34 /**
     35  * Calculate the base-2 logarithm of a number
     36  * @param {number} x
     37  * @returns {number}
     38  */
     39 
     40 export var log2 = /* #__PURE__ */Math.log2 || function log2(x) {
     41   return Math.log(x) / Math.LN2;
     42 };
     43 /**
     44  * Calculate the base-10 logarithm of a number
     45  * @param {number} x
     46  * @returns {number}
     47  */
     48 
     49 export var log10 = /* #__PURE__ */Math.log10 || function log10(x) {
     50   return Math.log(x) / Math.LN10;
     51 };
     52 /**
     53  * Calculate the natural logarithm of a number + 1
     54  * @param {number} x
     55  * @returns {number}
     56  */
     57 
     58 export var log1p = /* #__PURE__ */Math.log1p || function (x) {
     59   return Math.log(x + 1);
     60 };
     61 /**
     62  * Calculate cubic root for a number
     63  *
     64  * Code from es6-shim.js:
     65  *   https://github.com/paulmillr/es6-shim/blob/master/es6-shim.js#L1564-L1577
     66  *
     67  * @param {number} x
     68  * @returns {number} Returns the cubic root of x
     69  */
     70 
     71 export var cbrt = /* #__PURE__ */Math.cbrt || function cbrt(x) {
     72   if (x === 0) {
     73     return x;
     74   }
     75 
     76   var negate = x < 0;
     77   var result;
     78 
     79   if (negate) {
     80     x = -x;
     81   }
     82 
     83   if (isFinite(x)) {
     84     result = Math.exp(Math.log(x) / 3); // from https://en.wikipedia.org/wiki/Cube_root#Numerical_methods
     85 
     86     result = (x / (result * result) + 2 * result) / 3;
     87   } else {
     88     result = x;
     89   }
     90 
     91   return negate ? -result : result;
     92 };
     93 /**
     94  * Calculates exponentiation minus 1
     95  * @param {number} x
     96  * @return {number} res
     97  */
     98 
     99 export var expm1 = /* #__PURE__ */Math.expm1 || function expm1(x) {
    100   return x >= 2e-4 || x <= -2e-4 ? Math.exp(x) - 1 : x + x * x / 2 + x * x * x / 6;
    101 };
    102 /**
    103  * Formats a number in a given base
    104  * @param {number} n
    105  * @param {number} base
    106  * @param {number} size
    107  * @returns {string}
    108  */
    109 
    110 function formatNumberToBase(n, base, size) {
    111   var prefixes = {
    112     2: '0b',
    113     8: '0o',
    114     16: '0x'
    115   };
    116   var prefix = prefixes[base];
    117   var suffix = '';
    118 
    119   if (size) {
    120     if (size < 1) {
    121       throw new Error('size must be in greater than 0');
    122     }
    123 
    124     if (!isInteger(size)) {
    125       throw new Error('size must be an integer');
    126     }
    127 
    128     if (n > 2 ** (size - 1) - 1 || n < -(2 ** (size - 1))) {
    129       throw new Error("Value must be in range [-2^".concat(size - 1, ", 2^").concat(size - 1, "-1]"));
    130     }
    131 
    132     if (!isInteger(n)) {
    133       throw new Error('Value must be an integer');
    134     }
    135 
    136     if (n < 0) {
    137       n = n + 2 ** size;
    138     }
    139 
    140     suffix = "i".concat(size);
    141   }
    142 
    143   var sign = '';
    144 
    145   if (n < 0) {
    146     n = -n;
    147     sign = '-';
    148   }
    149 
    150   return "".concat(sign).concat(prefix).concat(n.toString(base)).concat(suffix);
    151 }
    152 /**
    153  * Convert a number to a formatted string representation.
    154  *
    155  * Syntax:
    156  *
    157  *    format(value)
    158  *    format(value, options)
    159  *    format(value, precision)
    160  *    format(value, fn)
    161  *
    162  * Where:
    163  *
    164  *    {number} value   The value to be formatted
    165  *    {Object} options An object with formatting options. Available options:
    166  *                     {string} notation
    167  *                         Number notation. Choose from:
    168  *                         'fixed'          Always use regular number notation.
    169  *                                          For example '123.40' and '14000000'
    170  *                         'exponential'    Always use exponential notation.
    171  *                                          For example '1.234e+2' and '1.4e+7'
    172  *                         'engineering'    Always use engineering notation.
    173  *                                          For example '123.4e+0' and '14.0e+6'
    174  *                         'auto' (default) Regular number notation for numbers
    175  *                                          having an absolute value between
    176  *                                          `lowerExp` and `upperExp` bounds, and
    177  *                                          uses exponential notation elsewhere.
    178  *                                          Lower bound is included, upper bound
    179  *                                          is excluded.
    180  *                                          For example '123.4' and '1.4e7'.
    181  *                         'bin', 'oct, or
    182  *                         'hex'            Format the number using binary, octal,
    183  *                                          or hexadecimal notation.
    184  *                                          For example '0b1101' and '0x10fe'.
    185  *                     {number} wordSize    The word size in bits to use for formatting
    186  *                                          in binary, octal, or hexadecimal notation.
    187  *                                          To be used only with 'bin', 'oct', or 'hex'
    188  *                                          values for 'notation' option. When this option
    189  *                                          is defined the value is formatted as a signed
    190  *                                          twos complement integer of the given word size
    191  *                                          and the size suffix is appended to the output.
    192  *                                          For example
    193  *                                          format(-1, {notation: 'hex', wordSize: 8}) === '0xffi8'.
    194  *                                          Default value is undefined.
    195  *                     {number} precision   A number between 0 and 16 to round
    196  *                                          the digits of the number.
    197  *                                          In case of notations 'exponential',
    198  *                                          'engineering', and 'auto',
    199  *                                          `precision` defines the total
    200  *                                          number of significant digits returned.
    201  *                                          In case of notation 'fixed',
    202  *                                          `precision` defines the number of
    203  *                                          significant digits after the decimal
    204  *                                          point.
    205  *                                          `precision` is undefined by default,
    206  *                                          not rounding any digits.
    207  *                     {number} lowerExp    Exponent determining the lower boundary
    208  *                                          for formatting a value with an exponent
    209  *                                          when `notation='auto`.
    210  *                                          Default value is `-3`.
    211  *                     {number} upperExp    Exponent determining the upper boundary
    212  *                                          for formatting a value with an exponent
    213  *                                          when `notation='auto`.
    214  *                                          Default value is `5`.
    215  *    {Function} fn    A custom formatting function. Can be used to override the
    216  *                     built-in notations. Function `fn` is called with `value` as
    217  *                     parameter and must return a string. Is useful for example to
    218  *                     format all values inside a matrix in a particular way.
    219  *
    220  * Examples:
    221  *
    222  *    format(6.4)                                        // '6.4'
    223  *    format(1240000)                                    // '1.24e6'
    224  *    format(1/3)                                        // '0.3333333333333333'
    225  *    format(1/3, 3)                                     // '0.333'
    226  *    format(21385, 2)                                   // '21000'
    227  *    format(12.071, {notation: 'fixed'})                // '12'
    228  *    format(2.3,    {notation: 'fixed', precision: 2})  // '2.30'
    229  *    format(52.8,   {notation: 'exponential'})          // '5.28e+1'
    230  *    format(12345678, {notation: 'engineering'})        // '12.345678e+6'
    231  *
    232  * @param {number} value
    233  * @param {Object | Function | number} [options]
    234  * @return {string} str The formatted value
    235  */
    236 
    237 
    238 export function format(value, options) {
    239   if (typeof options === 'function') {
    240     // handle format(value, fn)
    241     return options(value);
    242   } // handle special cases
    243 
    244 
    245   if (value === Infinity) {
    246     return 'Infinity';
    247   } else if (value === -Infinity) {
    248     return '-Infinity';
    249   } else if (isNaN(value)) {
    250     return 'NaN';
    251   } // default values for options
    252 
    253 
    254   var notation = 'auto';
    255   var precision;
    256   var wordSize;
    257 
    258   if (options) {
    259     // determine notation from options
    260     if (options.notation) {
    261       notation = options.notation;
    262     } // determine precision from options
    263 
    264 
    265     if (isNumber(options)) {
    266       precision = options;
    267     } else if (isNumber(options.precision)) {
    268       precision = options.precision;
    269     }
    270 
    271     if (options.wordSize) {
    272       wordSize = options.wordSize;
    273 
    274       if (typeof wordSize !== 'number') {
    275         throw new Error('Option "wordSize" must be a number');
    276       }
    277     }
    278   } // handle the various notations
    279 
    280 
    281   switch (notation) {
    282     case 'fixed':
    283       return toFixed(value, precision);
    284 
    285     case 'exponential':
    286       return toExponential(value, precision);
    287 
    288     case 'engineering':
    289       return toEngineering(value, precision);
    290 
    291     case 'bin':
    292       return formatNumberToBase(value, 2, wordSize);
    293 
    294     case 'oct':
    295       return formatNumberToBase(value, 8, wordSize);
    296 
    297     case 'hex':
    298       return formatNumberToBase(value, 16, wordSize);
    299 
    300     case 'auto':
    301       // remove trailing zeros after the decimal point
    302       return toPrecision(value, precision, options && options).replace(/((\.\d*?)(0+))($|e)/, function () {
    303         var digits = arguments[2];
    304         var e = arguments[4];
    305         return digits !== '.' ? digits + e : e;
    306       });
    307 
    308     default:
    309       throw new Error('Unknown notation "' + notation + '". ' + 'Choose "auto", "exponential", "fixed", "bin", "oct", or "hex.');
    310   }
    311 }
    312 /**
    313  * Split a number into sign, coefficients, and exponent
    314  * @param {number | string} value
    315  * @return {SplitValue}
    316  *              Returns an object containing sign, coefficients, and exponent
    317  */
    318 
    319 export function splitNumber(value) {
    320   // parse the input value
    321   var match = String(value).toLowerCase().match(/^(-?)(\d+\.?\d*)(e([+-]?\d+))?$/);
    322 
    323   if (!match) {
    324     throw new SyntaxError('Invalid number ' + value);
    325   }
    326 
    327   var sign = match[1];
    328   var digits = match[2];
    329   var exponent = parseFloat(match[4] || '0');
    330   var dot = digits.indexOf('.');
    331   exponent += dot !== -1 ? dot - 1 : digits.length - 1;
    332   var coefficients = digits.replace('.', '') // remove the dot (must be removed before removing leading zeros)
    333   .replace(/^0*/, function (zeros) {
    334     // remove leading zeros, add their count to the exponent
    335     exponent -= zeros.length;
    336     return '';
    337   }).replace(/0*$/, '') // remove trailing zeros
    338   .split('').map(function (d) {
    339     return parseInt(d);
    340   });
    341 
    342   if (coefficients.length === 0) {
    343     coefficients.push(0);
    344     exponent++;
    345   }
    346 
    347   return {
    348     sign: sign,
    349     coefficients: coefficients,
    350     exponent: exponent
    351   };
    352 }
    353 /**
    354  * Format a number in engineering notation. Like '1.23e+6', '2.3e+0', '3.500e-3'
    355  * @param {number | string} value
    356  * @param {number} [precision]        Optional number of significant figures to return.
    357  */
    358 
    359 export function toEngineering(value, precision) {
    360   if (isNaN(value) || !isFinite(value)) {
    361     return String(value);
    362   }
    363 
    364   var split = splitNumber(value);
    365   var rounded = roundDigits(split, precision);
    366   var e = rounded.exponent;
    367   var c = rounded.coefficients; // find nearest lower multiple of 3 for exponent
    368 
    369   var newExp = e % 3 === 0 ? e : e < 0 ? e - 3 - e % 3 : e - e % 3;
    370 
    371   if (isNumber(precision)) {
    372     // add zeroes to give correct sig figs
    373     while (precision > c.length || e - newExp + 1 > c.length) {
    374       c.push(0);
    375     }
    376   } else {
    377     // concatenate coefficients with necessary zeros
    378     // add zeros if necessary (for example: 1e+8 -> 100e+6)
    379     var missingZeros = Math.abs(e - newExp) - (c.length - 1);
    380 
    381     for (var i = 0; i < missingZeros; i++) {
    382       c.push(0);
    383     }
    384   } // find difference in exponents
    385 
    386 
    387   var expDiff = Math.abs(e - newExp);
    388   var decimalIdx = 1; // push decimal index over by expDiff times
    389 
    390   while (expDiff > 0) {
    391     decimalIdx++;
    392     expDiff--;
    393   } // if all coefficient values are zero after the decimal point and precision is unset, don't add a decimal value.
    394   // otherwise concat with the rest of the coefficients
    395 
    396 
    397   var decimals = c.slice(decimalIdx).join('');
    398   var decimalVal = isNumber(precision) && decimals.length || decimals.match(/[1-9]/) ? '.' + decimals : '';
    399   var str = c.slice(0, decimalIdx).join('') + decimalVal + 'e' + (e >= 0 ? '+' : '') + newExp.toString();
    400   return rounded.sign + str;
    401 }
    402 /**
    403  * Format a number with fixed notation.
    404  * @param {number | string} value
    405  * @param {number} [precision=undefined]  Optional number of decimals after the
    406  *                                        decimal point. null by default.
    407  */
    408 
    409 export function toFixed(value, precision) {
    410   if (isNaN(value) || !isFinite(value)) {
    411     return String(value);
    412   }
    413 
    414   var splitValue = splitNumber(value);
    415   var rounded = typeof precision === 'number' ? roundDigits(splitValue, splitValue.exponent + 1 + precision) : splitValue;
    416   var c = rounded.coefficients;
    417   var p = rounded.exponent + 1; // exponent may have changed
    418   // append zeros if needed
    419 
    420   var pp = p + (precision || 0);
    421 
    422   if (c.length < pp) {
    423     c = c.concat(zeros(pp - c.length));
    424   } // prepend zeros if needed
    425 
    426 
    427   if (p < 0) {
    428     c = zeros(-p + 1).concat(c);
    429     p = 1;
    430   } // insert a dot if needed
    431 
    432 
    433   if (p < c.length) {
    434     c.splice(p, 0, p === 0 ? '0.' : '.');
    435   }
    436 
    437   return rounded.sign + c.join('');
    438 }
    439 /**
    440  * Format a number in exponential notation. Like '1.23e+5', '2.3e+0', '3.500e-3'
    441  * @param {number | string} value
    442  * @param {number} [precision]  Number of digits in formatted output.
    443  *                              If not provided, the maximum available digits
    444  *                              is used.
    445  */
    446 
    447 export function toExponential(value, precision) {
    448   if (isNaN(value) || !isFinite(value)) {
    449     return String(value);
    450   } // round if needed, else create a clone
    451 
    452 
    453   var split = splitNumber(value);
    454   var rounded = precision ? roundDigits(split, precision) : split;
    455   var c = rounded.coefficients;
    456   var e = rounded.exponent; // append zeros if needed
    457 
    458   if (c.length < precision) {
    459     c = c.concat(zeros(precision - c.length));
    460   } // format as `C.CCCe+EEE` or `C.CCCe-EEE`
    461 
    462 
    463   var first = c.shift();
    464   return rounded.sign + first + (c.length > 0 ? '.' + c.join('') : '') + 'e' + (e >= 0 ? '+' : '') + e;
    465 }
    466 /**
    467  * Format a number with a certain precision
    468  * @param {number | string} value
    469  * @param {number} [precision=undefined] Optional number of digits.
    470  * @param {{lowerExp: number | undefined, upperExp: number | undefined}} [options]
    471  *                                       By default:
    472  *                                         lowerExp = -3 (incl)
    473  *                                         upper = +5 (excl)
    474  * @return {string}
    475  */
    476 
    477 export function toPrecision(value, precision, options) {
    478   if (isNaN(value) || !isFinite(value)) {
    479     return String(value);
    480   } // determine lower and upper bound for exponential notation.
    481 
    482 
    483   var lowerExp = options && options.lowerExp !== undefined ? options.lowerExp : -3;
    484   var upperExp = options && options.upperExp !== undefined ? options.upperExp : 5;
    485   var split = splitNumber(value);
    486   var rounded = precision ? roundDigits(split, precision) : split;
    487 
    488   if (rounded.exponent < lowerExp || rounded.exponent >= upperExp) {
    489     // exponential notation
    490     return toExponential(value, precision);
    491   } else {
    492     var c = rounded.coefficients;
    493     var e = rounded.exponent; // append trailing zeros
    494 
    495     if (c.length < precision) {
    496       c = c.concat(zeros(precision - c.length));
    497     } // append trailing zeros
    498     // TODO: simplify the next statement
    499 
    500 
    501     c = c.concat(zeros(e - c.length + 1 + (c.length < precision ? precision - c.length : 0))); // prepend zeros
    502 
    503     c = zeros(-e).concat(c);
    504     var dot = e > 0 ? e : 0;
    505 
    506     if (dot < c.length - 1) {
    507       c.splice(dot + 1, 0, '.');
    508     }
    509 
    510     return rounded.sign + c.join('');
    511   }
    512 }
    513 /**
    514  * Round the number of digits of a number *
    515  * @param {SplitValue} split       A value split with .splitNumber(value)
    516  * @param {number} precision  A positive integer
    517  * @return {SplitValue}
    518  *              Returns an object containing sign, coefficients, and exponent
    519  *              with rounded digits
    520  */
    521 
    522 export function roundDigits(split, precision) {
    523   // create a clone
    524   var rounded = {
    525     sign: split.sign,
    526     coefficients: split.coefficients,
    527     exponent: split.exponent
    528   };
    529   var c = rounded.coefficients; // prepend zeros if needed
    530 
    531   while (precision <= 0) {
    532     c.unshift(0);
    533     rounded.exponent++;
    534     precision++;
    535   }
    536 
    537   if (c.length > precision) {
    538     var removed = c.splice(precision, c.length - precision);
    539 
    540     if (removed[0] >= 5) {
    541       var i = precision - 1;
    542       c[i]++;
    543 
    544       while (c[i] === 10) {
    545         c.pop();
    546 
    547         if (i === 0) {
    548           c.unshift(0);
    549           rounded.exponent++;
    550           i++;
    551         }
    552 
    553         i--;
    554         c[i]++;
    555       }
    556     }
    557   }
    558 
    559   return rounded;
    560 }
    561 /**
    562  * Create an array filled with zeros.
    563  * @param {number} length
    564  * @return {Array}
    565  */
    566 
    567 function zeros(length) {
    568   var arr = [];
    569 
    570   for (var i = 0; i < length; i++) {
    571     arr.push(0);
    572   }
    573 
    574   return arr;
    575 }
    576 /**
    577  * Count the number of significant digits of a number.
    578  *
    579  * For example:
    580  *   2.34 returns 3
    581  *   0.0034 returns 2
    582  *   120.5e+30 returns 4
    583  *
    584  * @param {number} value
    585  * @return {number} digits   Number of significant digits
    586  */
    587 
    588 
    589 export function digits(value) {
    590   return value.toExponential().replace(/e.*$/, '') // remove exponential notation
    591   .replace(/^0\.?0*|\./, '') // remove decimal point and leading zeros
    592   .length;
    593 }
    594 /**
    595  * Minimum number added to one that makes the result different than one
    596  */
    597 
    598 export var DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16;
    599 /**
    600  * Compares two floating point numbers.
    601  * @param {number} x          First value to compare
    602  * @param {number} y          Second value to compare
    603  * @param {number} [epsilon]  The maximum relative difference between x and y
    604  *                            If epsilon is undefined or null, the function will
    605  *                            test whether x and y are exactly equal.
    606  * @return {boolean} whether the two numbers are nearly equal
    607 */
    608 
    609 export function nearlyEqual(x, y, epsilon) {
    610   // if epsilon is null or undefined, test whether x and y are exactly equal
    611   if (epsilon === null || epsilon === undefined) {
    612     return x === y;
    613   }
    614 
    615   if (x === y) {
    616     return true;
    617   } // NaN
    618 
    619 
    620   if (isNaN(x) || isNaN(y)) {
    621     return false;
    622   } // at this point x and y should be finite
    623 
    624 
    625   if (isFinite(x) && isFinite(y)) {
    626     // check numbers are very close, needed when comparing numbers near zero
    627     var diff = Math.abs(x - y);
    628 
    629     if (diff < DBL_EPSILON) {
    630       return true;
    631     } else {
    632       // use relative error
    633       return diff <= Math.max(Math.abs(x), Math.abs(y)) * epsilon;
    634     }
    635   } // Infinite and Number or negative Infinite and positive Infinite cases
    636 
    637 
    638   return false;
    639 }
    640 /**
    641  * Calculate the hyperbolic arccos of a number
    642  * @param {number} x
    643  * @return {number}
    644  */
    645 
    646 export var acosh = Math.acosh || function (x) {
    647   return Math.log(Math.sqrt(x * x - 1) + x);
    648 };
    649 export var asinh = Math.asinh || function (x) {
    650   return Math.log(Math.sqrt(x * x + 1) + x);
    651 };
    652 /**
    653  * Calculate the hyperbolic arctangent of a number
    654  * @param {number} x
    655  * @return {number}
    656  */
    657 
    658 export var atanh = Math.atanh || function (x) {
    659   return Math.log((1 + x) / (1 - x)) / 2;
    660 };
    661 /**
    662  * Calculate the hyperbolic cosine of a number
    663  * @param {number} x
    664  * @returns {number}
    665  */
    666 
    667 export var cosh = Math.cosh || function (x) {
    668   return (Math.exp(x) + Math.exp(-x)) / 2;
    669 };
    670 /**
    671  * Calculate the hyperbolic sine of a number
    672  * @param {number} x
    673  * @returns {number}
    674  */
    675 
    676 export var sinh = Math.sinh || function (x) {
    677   return (Math.exp(x) - Math.exp(-x)) / 2;
    678 };
    679 /**
    680  * Calculate the hyperbolic tangent of a number
    681  * @param {number} x
    682  * @returns {number}
    683  */
    684 
    685 export var tanh = Math.tanh || function (x) {
    686   var e = Math.exp(2 * x);
    687   return (e - 1) / (e + 1);
    688 };