| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- const hexRegex = /^[-+]?0x[a-fA-F0-9]+$/;
- const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/;
- // const octRegex = /^0x[a-z0-9]+/;
- // const binRegex = /0x[a-z0-9]+/;
- const consider = {
- hex: true,
- // oct: false,
- leadingZeros: true,
- decimalPoint: "\.",
- eNotation: true,
- //skipLike: /regex/,
- infinity: "original", // "null", "infinity" (Infinity type), "string" ("Infinity" (the string literal))
- };
- export default function toNumber(str, options = {}) {
- options = Object.assign({}, consider, options);
- if (!str || typeof str !== "string") return str;
- let trimmedStr = str.trim();
- if (trimmedStr.length === 0) return str;
- else if (options.skipLike !== undefined && options.skipLike.test(trimmedStr)) return str;
- else if (trimmedStr === "0") return 0;
- else if (options.hex && hexRegex.test(trimmedStr)) {
- return parse_int(trimmedStr, 16);
- // }else if (options.oct && octRegex.test(str)) {
- // return Number.parseInt(val, 8);
- } else if (!isFinite(trimmedStr)) { //Infinity
- return handleInfinity(str, Number(trimmedStr), options);
- } else if (trimmedStr.includes('e') || trimmedStr.includes('E')) { //eNotation
- return resolveEnotation(str, trimmedStr, options);
- // }else if (options.parseBin && binRegex.test(str)) {
- // return Number.parseInt(val, 2);
- } else {
- //separate negative sign, leading zeros, and rest number
- const match = numRegex.exec(trimmedStr);
- // +00.123 => [ , '+', '00', '.123', ..
- if (match) {
- const sign = match[1] || "";
- const leadingZeros = match[2];
- let numTrimmedByZeros = trimZeros(match[3]); //complete num without leading zeros
- const decimalAdjacentToLeadingZeros = sign ? // 0., -00., 000.
- str[leadingZeros.length + 1] === "."
- : str[leadingZeros.length] === ".";
- //trim ending zeros for floating number
- if (!options.leadingZeros //leading zeros are not allowed
- && (leadingZeros.length > 1
- || (leadingZeros.length === 1 && !decimalAdjacentToLeadingZeros))) {
- // 00, 00.3, +03.24, 03, 03.24
- return str;
- }
- else {//no leading zeros or leading zeros are allowed
- const num = Number(trimmedStr);
- const parsedStr = String(num);
- if (num === 0) return num;
- if (parsedStr.search(/[eE]/) !== -1) { //given number is long and parsed to eNotation
- if (options.eNotation) return num;
- else return str;
- } else if (trimmedStr.indexOf(".") !== -1) { //floating number
- if (parsedStr === "0") return num; //0.0
- else if (parsedStr === numTrimmedByZeros) return num; //0.456. 0.79000
- else if (parsedStr === `${sign}${numTrimmedByZeros}`) return num;
- else return str;
- }
- let n = leadingZeros ? numTrimmedByZeros : trimmedStr;
- if (leadingZeros) {
- // -009 => -9
- return (n === parsedStr) || (sign + n === parsedStr) ? num : str
- } else {
- // +9
- return (n === parsedStr) || (n === sign + parsedStr) ? num : str
- }
- }
- } else { //non-numeric string
- return str;
- }
- }
- }
- const eNotationRegx = /^([-+])?(0*)(\d*(\.\d*)?[eE][-\+]?\d+)$/;
- function resolveEnotation(str, trimmedStr, options) {
- if (!options.eNotation) return str;
- const notation = trimmedStr.match(eNotationRegx);
- if (notation) {
- let sign = notation[1] || "";
- const eChar = notation[3].indexOf("e") === -1 ? "E" : "e";
- const leadingZeros = notation[2];
- const eAdjacentToLeadingZeros = sign ? // 0E.
- str[leadingZeros.length + 1] === eChar
- : str[leadingZeros.length] === eChar;
- if (leadingZeros.length > 1 && eAdjacentToLeadingZeros) return str;
- else if (leadingZeros.length === 1
- && (notation[3].startsWith(`.${eChar}`) || notation[3][0] === eChar)) {
- return Number(trimmedStr);
- } else if (leadingZeros.length > 0) {
- // Has leading zeros — only accept if leadingZeros option allows it
- if (options.leadingZeros && !eAdjacentToLeadingZeros) {
- trimmedStr = (notation[1] || "") + notation[3];
- return Number(trimmedStr);
- } else return str;
- } else {
- // No leading zeros — always valid e-notation, parse it
- return Number(trimmedStr);
- }
- } else {
- return str;
- }
- }
- /**
- *
- * @param {string} numStr without leading zeros
- * @returns
- */
- function trimZeros(numStr) {
- if (numStr && numStr.indexOf(".") !== -1) {//float
- numStr = numStr.replace(/0+$/, ""); //remove ending zeros
- if (numStr === ".") numStr = "0";
- else if (numStr[0] === ".") numStr = "0" + numStr;
- else if (numStr[numStr.length - 1] === ".") numStr = numStr.substring(0, numStr.length - 1);
- return numStr;
- }
- return numStr;
- }
- function parse_int(numStr, base) {
- //polyfill
- if (parseInt) return parseInt(numStr, base);
- else if (Number.parseInt) return Number.parseInt(numStr, base);
- else if (window && window.parseInt) return window.parseInt(numStr, base);
- else throw new Error("parseInt, Number.parseInt, window.parseInt are not supported")
- }
- /**
- * Handle infinite values based on user option
- * @param {string} str - original input string
- * @param {number} num - parsed number (Infinity or -Infinity)
- * @param {object} options - user options
- * @returns {string|number|null} based on infinity option
- */
- function handleInfinity(str, num, options) {
- const isPositive = num === Infinity;
- switch (options.infinity.toLowerCase()) {
- case "null":
- return null;
- case "infinity":
- return num; // Return Infinity or -Infinity
- case "string":
- return isPositive ? "Infinity" : "-Infinity";
- case "original":
- default:
- return str; // Return original string like "1e1000"
- }
- }
|