DEV Community

Cover image for Converting Strings To Numbers in JS: Subtleties, Secrets, and Slip-ups
EmNudge
EmNudge

Posted on • Edited on

Converting Strings To Numbers in JS: Subtleties, Secrets, and Slip-ups

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.

console.log(Number("4"));    // 4
console.log(Number("4.01")); // 4.01
console.log(Number("42 g")); // NaN
Enter fullscreen mode Exit fullscreen mode

Note that this returns a different result than new Number().

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
Enter fullscreen mode Exit fullscreen mode

You can find more info on the MDN page.

Unary + Operator - conversion using +

Passing any string containing non-numeric values will return a NaN.

console.log(+"4");    // 4
console.log(+"4.01"); // 4.01
console.log(+"42 g"); // NaN
Enter fullscreen mode Exit fullscreen mode

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

const myNum = "4";
console.log(+myNum);    // 4
console.log(0 + myNum); // "04"
Enter fullscreen mode Exit fullscreen mode

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.

console.log(parseInt('0xF', 16)); // 15
console.log(parseInt('1111', 2)); // 15
console.log(parseInt(10, 10));    // 15
Enter fullscreen mode Exit fullscreen mode

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. 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.

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
Enter fullscreen mode Exit fullscreen mode

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.

// 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
Enter fullscreen mode Exit fullscreen mode

You can find more info on the MDN page

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).

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
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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. 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.

console.log(~4);      // -5
console.log(~~4);     // 4
console.log(~~4.311); // 4
Enter fullscreen mode Exit fullscreen mode

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().

console.log(~"4");      // -5
console.log(~~"4");     // 4
console.log(~~"4.311"); // 4
Enter fullscreen mode Exit fullscreen mode

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.

console.log(~"hat");  // -1
console.log(~~"hat"); // 0
console.log(~~"h4");  // 0
console.log(~~"4h");  // 0
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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.

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 has a fantastic example over on his blog.

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

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)