Converting Strings To Numbers in JS: Subtleties, Secrets, and Slip-ups
Published: 2019-07-09 23:35:18

In every programmer's life there comes a time when they must extract a number from a string. 
\**sniff*\* They grow up so fast...

In Javascript we don't typecast, but use functions (usually). We're going to go over a few different methods and explain them a bit more in depth.

## `Number()` - conversion using the Number class
This is probably a good default to rely on if you're unsure which one to use. Passing any string containing non-numeric values will return a `NaN`.
```js
console.log(Number("4"));    // 4
console.log(Number("4.01")); // 4.01
console.log(Number("42 g")); // NaN
```

Note that this returns a different result than `new Number()`.
```js
const myNum1 = Number("4");
console.log(myNum1);       // 4
console.log(myNum1 === 4); // true
console.log(myNum1 == 4);  // true

const myNum2 = new Number("4");
console.log(myNum2);       // Number {4}
console.log(myNum2 === 4); // false
console.log(myNum2 == 4);  // true
```


You can find more info on the [MDN page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number).

## Unary `+` Operator - conversion using `+`
Passing any string containing non-numeric values will return a `NaN`.

```js
console.log(+"4");    // 4
console.log(+"4.01"); // 4.01
console.log(+"42 g"); // NaN
```

This is *not* the same as the binary `+` operator. Same symbol, different context. Here's an example.

```js
const myNum = "4";
console.log(+myNum);    // 4
console.log(0 + myNum); // "04"
```

When adding a number with a string using the binary `+` operator, it will be coerced into a string. No, the spaces and order does not matter by the second example. Without a first parameter, the `+` changes to a unary operator and acts differently. This is obviously some very confusing and nuanced behavior.


## `parseInt()` - conversion to int
`parseInt()` is a part of both the `window` object and `Number` object. This means it can either be used as `parseInt()` or `Number.parseInt()`.

Passing it a string representation of a number, it will return an integer version.

`parseInt()` can also be passed a second parameter - the radix. This number describes in which base to process the number. e.g. Base 16, base 8, base 10, etc.
```js
console.log(parseInt('0xF', 16)); // 15
console.log(parseInt('1111', 2)); // 15
console.log(parseInt(10, 10));    // 15
```
Its default is base 10 in *most* instances, but it is highly recommended to add it in regardless since leaving it out can lead to [accidents](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt#Octal_interpretations_with_no_radix). [Google Apps Script](https://en.wikipedia.org/wiki/Google_Apps_Script), a pre-ES6 JS-like language used for dev work on the G Suite platform, defaults to octal if it has a leading 0. This is mostly for pre-ECMAscript 5, but it is still encouraged to include a radix.

One of the other reasons to use `parseInt()` over the alternatives is how it parses strings which include non-numeric characters. It will parse until it hits a non-numeric character and then stop. If it *starts* with a non-numeric character, it will return `NaN`.
```js
console.log(parseInt("4 aliens", 10));   // 4
console.log(parseInt("831million", 10)); // 831
console.log(parseInt("327 * 31", 10));   // 327
console.log(parseInt(".391", 10));       // NaN
```
Note: Due to this, unlike all other options, it cannot parse `"3.230e+2"` as `323`. It will return `3`.

This is incredibly useful for parsing CSS unit values. These usually include a number and then some unit type i.e. "150px", "20deg", "15rem", etc.
```js
// getting the css width of a tag with the class of "box"
const boxWidth = document.querySelector('.box').style.width;
console.log(boxWidth);               // 24px
console.log(parseInt(boxWidth, 10)); // 24
console.log(Number(boxWidth));       // NaN
```

You can find more info on the [MDN page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt)

## `parseFloat()` - conversion with decimals
`parseFloat()` is a part of both the `window` object and `Number` object. This means it can either be used as `parseFloat()` or `Number.parseFloat()`.

If the string does not contain a decimal, `parseFloat()` will not add one. It simply preserves them if there are any present.

Additionally, `parseFloat()` does not provide a radix option. What is the difference between `parseFloat()` and number then? `parseFloat()` also does not provide a `NaN` when a non-numeric character is present in the string (and is not the first character).

```js
console.log(parseFloat("4.5 pizzas left", 10)); // 4.5
console.log(parseFloat("3.230e+2", 10));        // 323
console.log(parseFloat("830.2 * 31.4", 10));    // 327.01
console.log(parseFloat(".391", 10));            // 0.391
console.log(parseFloat("one3.1", 10));          // NaN
```


## `Math` Methods - convert accidentally
The `Math` object contains many useful math-related functions. These functions also have a side-effect of converting non-numeric inputs as if it were using `Number()` or the unary `+` operator.

I will use `Math.pow(x, 1)` in the examples, but it is the same across all Math methods.

```js
console.log(Math.pow("32.5", 1)); // 32.5
console.log(Math.pow(".325", 1)); // 0.325
console.log(Math.pow("21 a", 1)); // NaN
```

This isn't preferable in any context. Unless of course your code's very purpose is to confuse the reader.

## Unary `~` Operator - bitwise unary conversion
The unary `~` operator is a [bitwise NOT operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#(Bitwise_NOT)). We're converting a base 10 number to base 2 and then converting back. Being a bitwise operator, any number is also cast to an integer, either through truncation or otherwise (I'm not quite sure of the method).

A bitwise NOT operator will flip all the bits. This results in a number represented by `-1 * (n + 1)` (assuming n is an integer). Repeating the same operation twice results in the original number.

```js
console.log(~4);      // -5
console.log(~~4);     // 4
console.log(~~4.311); // 4
```

While many quote a performance improvement over `Math.floor()` and use this instead, this operator can also be used to convert strings to integers in a similar way to `parseInt()`.
```js
console.log(~"4");      // -5
console.log(~~"4");     // 4
console.log(~~"4.311"); // 4
```

A major difference here, however, is that it will never return NaN. If the string is non-numeric or contains non-numeric characters, it will return `-1`. Preforming the operation again will preform a bitwise NOT on `-1`, resulting in `0`.
```js
console.log(~"hat");  // -1
console.log(~~"hat"); // 0
console.log(~~"h4");  // 0
console.log(~~"4h");  // 0
```

## Binary Bitwise Operators - conversion using bitwise binary
There are other bitwise methods we can use as well. While `~` is the only unary bitwise operator, we can use binary bitwise operators and pass useless values to the second operand.

```js
console.log("4"|0);   // 4
console.log("4"&-1);  // 4
console.log("4"<<0);  // 4
console.log("4">>0);  // 4
console.log("4">>>0); // 4
```

This way we're not really doing anything except for parsing it to an integer. Theoretically it should be faster than `~~`, but that's not exactly what we're going to see.

Find out more about [Bitwise Operators Here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators).

# Performance Testing

A lot of people are against performance testing. Sometimes specifics are hidden behind the engine in ways where our benchmarking tests will fail to provide accurate results. [Tom Dale](https://tomdale.net/2017/07/adventures-in-microbenchmarking/) has a fantastic example over on his [blog](https://tomdale.net/2017/07/adventures-in-microbenchmarking/).

Regardless, with that in mind, it's always a fun thing to look at. I'll be using [Benchmark.js](https://benchmarkjs.com) for these tests. If one option is only slightly higher (% wise) than the other, it's possible it would score lower on later tests.

```js
const num = "3249323242";
+num;            // operations: 6378907
~~num;           // operations: 6263920
num>>0;          // operations: 6256772
num<<0;          // operations: 6256384
num&-1;          // operations: 6239926
num>>>0;         // operations: 6125457
Number(num);     // operations: 6076579
num|0;           // operations: 5983553
parseFloat(num); // operations: 589328
parseInt(num);   // operations: 534935
Math.pow(num);   // operations: 485971
```
We can see that the bitwise operators were much faster than `parseInt()`, but only an insignificant amount faster than `Number()`. `Number()` is much easier to understand for your colleagues and future self, so be kind. 

# When To Use What

Use `parseInt()` for:
- converting a string from one base to another?
- getting a number from a CSS unit string? Use `parseInt`.

Use `parseFloat` for:
- getting a number from a CSS string that includes a decimal.

Use `Math` methods for:
- confusing your friends.

Use the unary `+` operator for:
- compressing/uglifying a JS file as a substitute for `Number()`.

Use the unary and binary bitwise operators for:
- compressing/uglifying a JS file as a substitute for `parseInt(x, 10)` where the radix and cutoff features aren't needed.
- highly performance dependent libraries with the above use case.

Use `Number`:
- every other time.

## TL;DR

Each method does something slightly different. Use `Number()` whenever you're unsure. If you want specifics, you're going to have to read the article.