Due to the type coercion mechanism of JavaScript, there exist numerous methods to convert a variable to a number. But most of them share the same conversion logic. We can classify these in 5 groups:
parseInt(x, 10);
parseInt(x);
// Note: These radices will output a different values for null, undefined, true, false, and NaN (due to coercion to String):
parseInt(false, 16); // 16 & up for false (f becomes a valid character)
parseInt(null, 24); // 24 & up for null or NaN (n becomes a valid character)
parseInt(NaN, 24);
parseInt(true, 30); // 30 & up for true (t becomes a valid character)
parseInt(undefined, 31); // 31 & up for undefined (u becomes a valid character)
parseFloat(x);
// there is no version of parseFloat that accepts a radix. The number is always in base 10.
Number(x)
+x
x ** 1
x * 1
x / 1
x | 0
x >> 0
x << 0
x >>> 0
false | null | true | undefined | Symbol(some symbol) | |
---|---|---|---|---|---|
parseInt(x, 10) | NaN | NaN | NaN | NaN | "TypeError" |
parseInt(x, 16) | 250 | NaN | NaN | NaN | "TypeError" |
parseInt(x, 24) | 8901 | 23 | NaN | NaN | "TypeError" |
parseInt(x, 30) | 12439754 | 23 | 897 | NaN | "TypeError" |
parseInt(x, 31) | 14171788 | 714695 | 890830 | 26231474015353 | "TypeError" |
parseInt(x) | NaN | NaN | NaN | NaN | "TypeError" |
parseFloat(x) | NaN | NaN | NaN | NaN | "TypeError" |
Number(x) | 0 | 0 | 1 | NaN | "TypeError" |
right shift: x >> 0 | 0 | 0 | 1 | 0 | "TypeError" |
uright shift: x >>> 0 | 0 | 0 | 1 | 0 | "TypeError" |
Note: TypeError
is thrown and not returned.
{} | { toString } | { toPrimitive } | { toPrimitive, toString } | { valueOf } | { toPrimitive, toString, valueOf } | |
---|---|---|---|---|---|---|
parseInt(x, 10) | NaN | 200 | 100 | 100 | NaN | 100 |
parseInt(x) | NaN | 200 | 100 | 100 | NaN | 100 |
parseFloat(x) | NaN | 200 | 100 | 100 | NaN | 100 |
Number(x) | NaN | 200 | 50 | 50 | 300 | 50 |
right shift: x >> 0 | 0 | 200 | 50 | 50 | 300 | 50 |
uright shift: x >>> 0 | 0 | 200 | 50 | 50 | 300 | 50 |
Note, in the above objects:
toPrimitive
is a method that returns50
if the primitive hint is'number'
, and'100'
is the hint is'string'
toString
is a method that returns'200'
valueOf
is a method that returns300
(as a number)
0 | -0 | NaN | 0.45 | 1.55 | MAX_SAFE_INTEGER | MIN_SAFE_INTEGER | MAX_VALUE | -MAX_VALUE | MIN_VALUE | |
---|---|---|---|---|---|---|---|---|---|---|
parseInt(x, 10) | 0 | 0 | NaN | 0 | 1 | MAX_SAFE_INTEGER | MIN_SAFE_INTEGER | 1 | -1 | 5 |
parseInt(x, 24) | 0 | 0 | 13511 | 0 | 1 | 4.543973305368212e+21 | -4.543973305368212e+21 | 1 | -1 | 134 |
parseInt(x) | 0 | 0 | NaN | 0 | 1 | MAX_SAFE_INTEGER | MIN_SAFE_INTEGER | 1 | -1 | 5 |
parseFloat(x) | 0 | 0 | NaN | 0.45 | 1.55 | MAX_SAFE_INTEGER | MIN_SAFE_INTEGER | MAX_VALUE | -MAX_VALUE | MIN_VALUE |
Number(x) | 0 | -0 | NaN | 0.45 | 1.55 | MAX_SAFE_INTEGER | MIN_SAFE_INTEGER | MAX_VALUE | -MAX_VALUE | MIN_VALUE |
right shift: x >> 0 | 0 | 0 | 0 | 0 | 1 | -1 | 1 | 0 | 0 | 0 |
uright shift: x >>> 0 | 0 | 0 | 0 | 0 | 1 | 4294967295 | 1 | 0 | 0 | 0 |
Note: to reduce the noise, some inputs have been replaced by a name. The actual inputs are the contents of the static properties of the Number
object that share the same name as these.
"45" | " 45 " | "45px" | "NaN" | "px45" | "1e2" | "0b10" | "0xff" | |
---|---|---|---|---|---|---|---|---|
parseInt(x, 10) | 45 | 45 | 45 | NaN | NaN | 1 | 0 | 0 |
parseInt(x) | 45 | 45 | 45 | NaN | NaN | 1 | 0 | 255 |
parseFloat(x) | 45 | 45 | 45 | NaN | NaN | 100 | 0 | 0 |
Number(x) | 45 | 45 | NaN | NaN | NaN | 100 | 2 | 255 |
right shift: x >> 0 | 45 | 45 | 0 | 0 | 0 | 100 | 2 | 255 |
uright shift: x >>> 0 | 45 | 45 | 0 | 0 | 0 | 100 | 2 | 255 |
"0.45" | ".45" | "1e-2" | ".1e-2" | "0b10.10" | "0xff.ff" | |
---|---|---|---|---|---|---|
parseInt(x, 10) | 0 | NaN | 1 | NaN | 0 | 0 |
parseInt(x) | 0 | NaN | 1 | NaN | 0 | 255 |
parseFloat(x) | 0.45 | 0.45 | 0.01 | 0.001 | 0 | 0 |
Number(x) | 0.45 | 0.45 | 0.01 | 0.001 | NaN | NaN |
right shift: x >> 0 | 0 | 0 | 0 | 0 | 0 | 0 |
uright shift: x >>> 0 | 0 | 0 | 0 | 0 | 0 | 0 |
"" | "+0" | "-0" | MAX_SAFE_INTEGER (as string) | MIN_SAFE_INTEGER (as string) | MAX_VALUE (as string) | -MAX_VALUE (as string) | > MAX_VALUE (as string) | < -MAX_VALUE (as string) | MIN_VALUE (as string) | LOW_PRECISION (as string) | |
---|---|---|---|---|---|---|---|---|---|---|---|
parseInt(x, 10) | NaN | 0 | -0 | MAX_SAFE_INTEGER | MIN_SAFE_INTEGER | 1 | -1 | 1 | -1 | 5 | 1 |
parseInt(x) | NaN | 0 | -0 | MAX_SAFE_INTEGER | MIN_SAFE_INTEGER | 1 | -1 | 1 | -1 | 5 | 1 |
parseFloat(x) | NaN | 0 | -0 | MAX_SAFE_INTEGER | MIN_SAFE_INTEGER | MAX_VALUE | -MAX_VALUE | +Infinity | -Infinity | MIN_VALUE | 0 |
Number(x) | 0 | 0 | -0 | MAX_SAFE_INTEGER | MIN_SAFE_INTEGER | MAX_VALUE | -MAX_VALUE | +Infinity | -Infinity | MIN_VALUE | 0 |
right shift: x >> 0 | 0 | 0 | 0 | -1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
uright shift: x >>> 0 | 0 | 0 | 0 | 4294967295 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
Note: to reduce the noise, some inputs have been replaced by a name. The actual inputs are:
- MAX_VALUE (as string):
"1.7976931348623157e+308"
- > MAX_VALUE (as string):
"1.7976931348623157e+309"
- -MAX_VALUE (as string):
"-1.7976931348623157e+308"
- < -MAX_VALUE (as string):
"-1.7976931348623157e+309"
- MIN_VALUE (as string):
"5e-324"
- LOW_PRECISION (as string):
"1.1e-324"
- MAX_SAFE_INTEGER (as string):
"9007199254740991"
- MIN_SAFE_INTEGER (as string):
"-9007199254740991"
In the future: https://github.com/mathiasbynens/proposal-number-fromstring
This is the best method I could come up with that parses numbers in a sane way.
You can then use the various Number.*
methods to filter out the kind of numbers (Integer, Infinity, Float, etc...)
/**
* This function parses strings, numbers and objects into numbers in the sanest way possible.
* It allows the largest subset of syntax possible without returning unexpected results.
*
* - Only parses strings containing valid JavaScript numbers (Infinity and such are valid, numeric separators too)
* - No unsafe radix detection like parseInt (0xffff, 0b01, 0o777 are ok because the radix is explicit)
* - Only parses objects that have a toPrimitive method which supports the `number` hint, or valueOf that returns a number (no more array to number conversion).
*/
function saneParseNumber(n) {
// avoid NPE
if (n === null) {
return Number.NaN;
}
const type = typeof n;
if (type === 'number') {
return n;
}
if (type === 'string') {
// prevent empty string from being coerced into 0
if (n.trim() === '') {
return Number.NaN;
}
// Use coercion instead of parseFloat to allow formats such as "0xffff" and forbid formats such as "35INVALID"
return Number(n);
}
if (type === 'object') {
// One could argue that these methods should be wrapped in a try-catch that returns NaN if the method throws
if (Symbol.toPrimitive in n) {
const asNumber = n[Symbol.toPrimitive]('number');
if (typeof asNumber === 'number') {
return asNumber;
}
}
if ('valueOf' in n) {
const asNumber = n.valueOf();
if (typeof asNumber === 'number') {
return asNumber;
}
}
}
return Number.NaN;
}
Proposals that will likely influence the results: