-
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathstrnum.js
More file actions
171 lines (155 loc) · 6.46 KB
/
Copy pathstrnum.js
File metadata and controls
171 lines (155 loc) · 6.46 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
162
163
164
165
166
167
168
169
170
171
const hexRegex = /^[-+]?0x[a-fA-F0-9]+$/;
const binRegex = /^0b[01]+$/;
const octRegex = /^0o[0-7]+$/;
const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/;
import anynum from "anynum";
const consider = {
hex: true,
binary: false,
octal: false,
leadingZeros: true,
decimalPoint: "\.",
eNotation: true,
//skipLike: /regex/,
infinity: "original", // "null", "infinity" (Infinity type), "string" ("Infinity" (the string literal))
unicode: false,
};
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;
if (options.unicode) {
trimmedStr = anynum(trimmedStr);
if (trimmedStr === "0") return 0; // re-check after normalization
}
if (options.hex && hexRegex.test(trimmedStr)) {
return parse_int(trimmedStr, 16);
} else if (options.binary && binRegex.test(trimmedStr)) {
return parse_int(trimmedStr, 2);
} else if (options.octal && octRegex.test(trimmedStr)) {
return parse_int(trimmedStr, 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 {
//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) {
const str = numStr.trim();
if (base === 2 || base === 8) numStr = str.substring(2);
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"
}
}