Gets the name of a number, even if it's stupidly big. Supersedes TheoryOfNekomata/number-name.
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

124 рядки
4.1 KiB

  1. /**
  2. * Valid values that can be converted to exponential notation.
  3. */
  4. export type ValidValue = string | number | bigint;
  5. /**
  6. * Options to use when converting a number to exponential notation.
  7. */
  8. export interface NumberToExponentialOptions {
  9. /**
  10. * The decimal point character to use. Defaults to ".".
  11. */
  12. decimalPoint?: string;
  13. /**
  14. * The grouping symbol to use. Defaults to ",".
  15. */
  16. groupingSymbol?: string;
  17. /**
  18. * Exponent character to use. Defaults to "e".
  19. */
  20. exponentDelimiter?: string;
  21. }
  22. /**
  23. * Extracts the integer, fractional, and exponent components of a string in exponential notation.
  24. * @param value - The string value to extract components from.
  25. * @param options - Options to use when extracting components.
  26. */
  27. export const extractExponentialComponents = (value: string, options = {} as NumberToExponentialOptions) => {
  28. const {
  29. decimalPoint = '.',
  30. groupingSymbol = ',',
  31. exponentDelimiter = 'e',
  32. } = options;
  33. const valueWithoutGroupingSymbols = value.replace(new RegExp(`${groupingSymbol}`, 'g'), '');
  34. const exponentDelimiterIndex = valueWithoutGroupingSymbols.indexOf(exponentDelimiter);
  35. if (exponentDelimiterIndex < 0) {
  36. // We force the value to have decimal point so that we can extract the integer and fractional
  37. // components.
  38. const stringValueWithDecimal = valueWithoutGroupingSymbols.includes(decimalPoint)
  39. ? valueWithoutGroupingSymbols
  40. : `${valueWithoutGroupingSymbols}${decimalPoint}0`;
  41. const [integerRaw, fractionalRaw] = stringValueWithDecimal.split(decimalPoint);
  42. const integer = integerRaw.replace(/^0+/g, '');
  43. const exponentValue = BigInt(integer.length - 1);
  44. return {
  45. integer,
  46. exponent: exponentValue < 0 ? exponentValue.toString() : `+${exponentValue}`,
  47. fractional: fractionalRaw.replace(/0+$/g, ''),
  48. };
  49. }
  50. if (exponentDelimiterIndex !== valueWithoutGroupingSymbols.lastIndexOf(exponentDelimiter)) {
  51. throw new TypeError('Value must not contain more than one exponent character');
  52. }
  53. const [base, exponentRaw] = valueWithoutGroupingSymbols.split(exponentDelimiter);
  54. const [integerRaw, fractionalRaw = ''] = base.split(decimalPoint);
  55. const integerWithoutZeroes = integerRaw.replace(/^0+/g, '');
  56. const integer = integerWithoutZeroes[0] ?? '0';
  57. const extraIntegerDigits = integerWithoutZeroes.slice(1);
  58. const fractional = `${extraIntegerDigits}${fractionalRaw.replace(/0+$/g, '')}`;
  59. const exponentValue = BigInt(exponentRaw) + BigInt(extraIntegerDigits.length);
  60. return {
  61. integer,
  62. exponent: exponentValue < 0 ? exponentValue.toString() : `+${exponentValue}`,
  63. fractional,
  64. };
  65. }
  66. /**
  67. * Converts a numeric value to a string in exponential notation. Supports numbers of all types.
  68. * @param value - The value to convert.
  69. * @param options - Options to use when extracting components.
  70. */
  71. export const numberToExponential = (value: ValidValue, options = {} as NumberToExponentialOptions): string => {
  72. const stringValueRaw = value as unknown
  73. if (typeof stringValueRaw === 'bigint' || typeof stringValueRaw === 'number') {
  74. return numberToExponential(stringValueRaw.toString(), options);
  75. }
  76. if (typeof stringValueRaw !== 'string') {
  77. throw new TypeError(`Value must be a string, number, or bigint. Received: ${typeof stringValueRaw}`);
  78. }
  79. if (stringValueRaw.startsWith('-')) {
  80. return `-${numberToExponential(stringValueRaw.slice(1), options)}}`;
  81. }
  82. const {
  83. decimalPoint = '.',
  84. groupingSymbol = ',',
  85. exponentDelimiter = 'e',
  86. } = options;
  87. const stringValue = stringValueRaw
  88. .replace(new RegExp(`${groupingSymbol}`, 'g'), '')
  89. .toLowerCase()
  90. .replace(/\s/g, '');
  91. const {
  92. integer,
  93. fractional,
  94. exponent,
  95. } = extractExponentialComponents(stringValue, options);
  96. const significantDigits = `${integer}${fractional}`;
  97. if (significantDigits.length === 0) {
  98. // We copy the behavior from `Number.prototype.toExponential` here.
  99. return `0${exponentDelimiter}+0`;
  100. }
  101. const significandInteger = significantDigits[0];
  102. const significandFractional = significantDigits.slice(1).replace(/0+$/g, '');
  103. if (significandFractional.length === 0) {
  104. return `${significandInteger}${exponentDelimiter}${exponent}`;
  105. }
  106. return `${significandInteger}${decimalPoint}${significandFractional}${exponentDelimiter}${exponent}`;
  107. };