Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active June 28, 2020 21:32
Show Gist options
  • Save dfkaye/a9ae55375ecef4520800aaa7a67e2182 to your computer and use it in GitHub Desktop.
Save dfkaye/a9ae55375ecef4520800aaa7a67e2182 to your computer and use it in GitHub Desktop.
s2n.js: get numeric value contained in a string; currency-comma-whitespace-friendly version of parseInt et al.
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
// 8 March 2019: regexp with Number(n) output.
// 10 March 2019: updated with tests and a function name.
// 11 March 2019: change void 0 to null, fix comment mistakes.
// 28 June 2020: added trim and negation test so "-$200" returns -200, not 200.
/*
* @function s2n is a currency-comma-and-whitespace-friendly version of built-in
* functions Number, parseFloat(), and parseInt(value, radix) without the radix
* argument.
*
* Examples of built-in uselessness:
*
* Number("-1,020.9") returns NaN - can't handle commas.
* parseFloat("-1,020.9") returns -1 - exits at first comma.
* parseInt("-1,020.9") returns -1 - exits at first comma.
*
* With currencies, the problem is uniformly useless, across the board:
*
* Number("$1,020.9") returns NaN.
* parseFloat("$1,020.9") returns NaN.
* parseInt("$1,020.9") returns NaN.
*
* `NaN` is not a value, it is only information about a value's "type" - which
* we don't care about. We want the converted value, when one can be found, or
* a sensible fallback value such as `0` when one cannot.
*
* s2n processes all numeric characters, ignores all non-numerics, and returns
* the numeric value contained in the string.
*
* If a string has no value or results in `NaN`, s2n returns `0`.
*
* Examples:
* s2n("$200") returns the number, `200`.
* s2n("4,000.015") returns the number, `4000.015`.
* s2n("6 999 999 . 999") returns the number, `6999999.999`.
* s2n("7 a b 8") returns the number, `78`.
* s2n("$blah.blah") returns the number, `0`.
* s2n("-0") returns the number, `0`.
* s2n("-x") returns the number, `0`.
* s2n("-$200") returns the number, `-200`.
*
* Function does not process possible expressions, such as "1 + 1". s2n("1 + 1")
* returns the number, `11`, not the number `2`.
*
* Negative hexadecimal strings, e.g., "-0x333", are not well supported yet.
*
* @param {number|string} s
* @returns {number}
*/
function s2n(s) {
var n = Number(s);
if (n === n) {
return n;
}
var t = String(s).trim();
var m = t.match(/[\d\.]/g);
var v = m ? m.join('') : null;
n = Number(v);
// return number or 0, not -0, not NaN
return n === n
? t.charAt(0) === "-" && n
? n * -1
: n + 0
: 0;
}
/* test it out */
var tests = [
// decimal with no leading 0
".01",
// 0 is 0
"0.0",
// decimal with leading zero
"0.99",
// octal notation
"08",
// integer
"1",
// currency
"$200",
// decimal
"300.015",
// comma
"4,000.015",
// whitespace
"5 999 999 . 999",
// larger than maximum safe integer
"9478123289889839898438984843498934894",
// alpha numeric
"7 a b 8",
// no value
"$blah.blah",
// no value
"x",
// operation is not a value
"1 + 1",
// a number
555,
// hexadecimal number
0x333,
// hexadecimal string
"0x333"
];
var results = [].concat(
"*positive values*",
tests.map(function(s) { return { input: String(s), result: s2n(s) }; }),
"*negative values*",
tests.map(function(s) { return { input: '-' + s, result: s2n('-' + s) }; })
);
console.dir(results);
/*Array(36)
0: "*positive values*"
1: {input: ".01", result: 0.01}
2: {input: "0.0", result: 0}
3: {input: "0.99", result: 0.99}
4: {input: "08", result: 8}
5: {input: "1", result: 1}
6: {input: "$200", result: 200}
7: {input: "300.015", result: 300.015}
8: {input: "4,000.015", result: 4000.015}
9: {input: "5 999 999 . 999", result: 5999999.999}
10: {input: "9478123289889839898438984843498934894", result: 9.47812328988984e+36}
11: {input: "7 a b 8", result: 78}
12: {input: "$blah.blah", result: 0}
13: {input: "x", result: 0}
14: {input: "1 + 1", result: 11}
15: {input: "555", result: 555}
16: {input: "819", result: 819} // Surprising result: 0x333 is converted to decimal
17: {input: "0x333", result: 819} // Surprising result: "0x333" is converted to decimal
18: "*negative values*"
19: {input: "-.01", result: -0.01}
20: {input: "-0.0", result: -0}
21: {input: "-0.99", result: -0.99}
22: {input: "-08", result: -8}
23: {input: "-1", result: -1}
24: {input: "-$200", result: -200}
25: {input: "-300.015", result: -300.015}
26: {input: "-4,000.015", result: -4000.015}
27: {input: "-5 999 999 . 999", result: -5999999.999}
28: {input: "-9478123289889839898438984843498934894", result: -9.47812328988984e+36}
29: {input: "-7 a b 8", result: -78}
30: {input: "-$blah.blah", result: 0}
31: {input: "-x", result: 0}
32: {input: "-1 + 1", result: -11}
33: {input: "-555", result: -555}
34: {input: "-819", result: -819} // Surprising result: -0x333 is converted to decimal
35: {input: "-0x333", result: -333} // Surprising result: Number("-0x333") returns NaN
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment