-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathstrnum.js
More file actions
161 lines (147 loc) · 6.2 KB
/
strnum.js
File metadata and controls
161 lines (147 loc) · 6.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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"
}
}