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 };