Skip to content

Instantly share code, notes, and snippets.

@ephys
Last active December 19, 2017 21:50
Show Gist options
  • Save ephys/4d9731d5c7a945dcc752d279eaeaa08f to your computer and use it in GitHub Desktop.
Save ephys/4d9731d5c7a945dcc752d279eaeaa08f to your computer and use it in GitHub Desktop.

Parsing Numbers in JavaScript

Techniques

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

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

parseFloat(x);
// there is no version of parseFloat that accepts a radix. The number is always in base 10.

Number & arithmetic operators

Number(x)
+x
x ** 1
x * 1
x / 1

Bitwise operators

x | 0
x >> 0
x << 0

Unsigned bitwise operators

x >>> 0

Results

Primitives => Numbers

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.

Objects => Numbers

{} { 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 returns 50 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 returns 300 (as a number)

Numbers => Numbers

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.

Strings => Numbers (integers)

"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

Strings => Numbers (decimals)

"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

Strings => Numbers (special numbers)

"" "+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"

But then how do I parse a number?

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;
}

Future

Proposals that will likely influence the results:

Sources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment