strnum.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. const hexRegex = /^[-+]?0x[a-fA-F0-9]+$/;
  2. const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/;
  3. // const octRegex = /^0x[a-z0-9]+/;
  4. // const binRegex = /0x[a-z0-9]+/;
  5. const consider = {
  6. hex: true,
  7. // oct: false,
  8. leadingZeros: true,
  9. decimalPoint: "\.",
  10. eNotation: true,
  11. //skipLike: /regex/,
  12. infinity: "original", // "null", "infinity" (Infinity type), "string" ("Infinity" (the string literal))
  13. };
  14. export default function toNumber(str, options = {}) {
  15. options = Object.assign({}, consider, options);
  16. if (!str || typeof str !== "string") return str;
  17. let trimmedStr = str.trim();
  18. if (trimmedStr.length === 0) return str;
  19. else if (options.skipLike !== undefined && options.skipLike.test(trimmedStr)) return str;
  20. else if (trimmedStr === "0") return 0;
  21. else if (options.hex && hexRegex.test(trimmedStr)) {
  22. return parse_int(trimmedStr, 16);
  23. // }else if (options.oct && octRegex.test(str)) {
  24. // return Number.parseInt(val, 8);
  25. } else if (!isFinite(trimmedStr)) { //Infinity
  26. return handleInfinity(str, Number(trimmedStr), options);
  27. } else if (trimmedStr.includes('e') || trimmedStr.includes('E')) { //eNotation
  28. return resolveEnotation(str, trimmedStr, options);
  29. // }else if (options.parseBin && binRegex.test(str)) {
  30. // return Number.parseInt(val, 2);
  31. } else {
  32. //separate negative sign, leading zeros, and rest number
  33. const match = numRegex.exec(trimmedStr);
  34. // +00.123 => [ , '+', '00', '.123', ..
  35. if (match) {
  36. const sign = match[1] || "";
  37. const leadingZeros = match[2];
  38. let numTrimmedByZeros = trimZeros(match[3]); //complete num without leading zeros
  39. const decimalAdjacentToLeadingZeros = sign ? // 0., -00., 000.
  40. str[leadingZeros.length + 1] === "."
  41. : str[leadingZeros.length] === ".";
  42. //trim ending zeros for floating number
  43. if (!options.leadingZeros //leading zeros are not allowed
  44. && (leadingZeros.length > 1
  45. || (leadingZeros.length === 1 && !decimalAdjacentToLeadingZeros))) {
  46. // 00, 00.3, +03.24, 03, 03.24
  47. return str;
  48. }
  49. else {//no leading zeros or leading zeros are allowed
  50. const num = Number(trimmedStr);
  51. const parsedStr = String(num);
  52. if (num === 0) return num;
  53. if (parsedStr.search(/[eE]/) !== -1) { //given number is long and parsed to eNotation
  54. if (options.eNotation) return num;
  55. else return str;
  56. } else if (trimmedStr.indexOf(".") !== -1) { //floating number
  57. if (parsedStr === "0") return num; //0.0
  58. else if (parsedStr === numTrimmedByZeros) return num; //0.456. 0.79000
  59. else if (parsedStr === `${sign}${numTrimmedByZeros}`) return num;
  60. else return str;
  61. }
  62. let n = leadingZeros ? numTrimmedByZeros : trimmedStr;
  63. if (leadingZeros) {
  64. // -009 => -9
  65. return (n === parsedStr) || (sign + n === parsedStr) ? num : str
  66. } else {
  67. // +9
  68. return (n === parsedStr) || (n === sign + parsedStr) ? num : str
  69. }
  70. }
  71. } else { //non-numeric string
  72. return str;
  73. }
  74. }
  75. }
  76. const eNotationRegx = /^([-+])?(0*)(\d*(\.\d*)?[eE][-\+]?\d+)$/;
  77. function resolveEnotation(str, trimmedStr, options) {
  78. if (!options.eNotation) return str;
  79. const notation = trimmedStr.match(eNotationRegx);
  80. if (notation) {
  81. let sign = notation[1] || "";
  82. const eChar = notation[3].indexOf("e") === -1 ? "E" : "e";
  83. const leadingZeros = notation[2];
  84. const eAdjacentToLeadingZeros = sign ? // 0E.
  85. str[leadingZeros.length + 1] === eChar
  86. : str[leadingZeros.length] === eChar;
  87. if (leadingZeros.length > 1 && eAdjacentToLeadingZeros) return str;
  88. else if (leadingZeros.length === 1
  89. && (notation[3].startsWith(`.${eChar}`) || notation[3][0] === eChar)) {
  90. return Number(trimmedStr);
  91. } else if (leadingZeros.length > 0) {
  92. // Has leading zeros — only accept if leadingZeros option allows it
  93. if (options.leadingZeros && !eAdjacentToLeadingZeros) {
  94. trimmedStr = (notation[1] || "") + notation[3];
  95. return Number(trimmedStr);
  96. } else return str;
  97. } else {
  98. // No leading zeros — always valid e-notation, parse it
  99. return Number(trimmedStr);
  100. }
  101. } else {
  102. return str;
  103. }
  104. }
  105. /**
  106. *
  107. * @param {string} numStr without leading zeros
  108. * @returns
  109. */
  110. function trimZeros(numStr) {
  111. if (numStr && numStr.indexOf(".") !== -1) {//float
  112. numStr = numStr.replace(/0+$/, ""); //remove ending zeros
  113. if (numStr === ".") numStr = "0";
  114. else if (numStr[0] === ".") numStr = "0" + numStr;
  115. else if (numStr[numStr.length - 1] === ".") numStr = numStr.substring(0, numStr.length - 1);
  116. return numStr;
  117. }
  118. return numStr;
  119. }
  120. function parse_int(numStr, base) {
  121. //polyfill
  122. if (parseInt) return parseInt(numStr, base);
  123. else if (Number.parseInt) return Number.parseInt(numStr, base);
  124. else if (window && window.parseInt) return window.parseInt(numStr, base);
  125. else throw new Error("parseInt, Number.parseInt, window.parseInt are not supported")
  126. }
  127. /**
  128. * Handle infinite values based on user option
  129. * @param {string} str - original input string
  130. * @param {number} num - parsed number (Infinity or -Infinity)
  131. * @param {object} options - user options
  132. * @returns {string|number|null} based on infinity option
  133. */
  134. function handleInfinity(str, num, options) {
  135. const isPositive = num === Infinity;
  136. switch (options.infinity.toLowerCase()) {
  137. case "null":
  138. return null;
  139. case "infinity":
  140. return num; // Return Infinity or -Infinity
  141. case "string":
  142. return isPositive ? "Infinity" : "-Infinity";
  143. case "original":
  144. default:
  145. return str; // Return original string like "1e1000"
  146. }
  147. }