JS Tip: Use parseInt for strings, NOT for numbers

June 6, 2017    javascript math parseint rounding bitwise floats

So you have a decimal number that you want converted into an integer. You’ve come across parseInt and think to yourself, “Boy, that sounds like the right function for me!”. So you try the following.

parseInt(1.655)
-> 1

Great. It worked!

But later on…

parseInt(0.0000000060)
-> 6

Whoops. Clearly something went wrong here.

So what happened? If you cast 0.000000006 as a Number in your DevTools console, you’ll see the value 6e-9. This is a shorthand mathematical form used when there’s lots of consecutive 0s.

Let’s quickly go to the MDN spec to see what parseInt actually does.

The parseInt() function parses a string argument and returns an integer of the specified radix (the base in mathematical numeral systems).

If the string argument is not a string, then it is converted to a string (using the ToString abstract operation). – MDN

Back to our problem, what has happened is that the number 0.000000006 was first converted to a string, which is "6e-9", as per the spec.

It then tried to parse the string, but came across an e character, which is not a number. Well, technically it is! It’s approximately 2.718, but let’s not get into logarithms! It bails out at e, and returns what it has to that point. In this case, 6.

If we now perform the same action using a string representation of the original number, we get the correct answer:

parseInt('0.000000006')
> 0

It’s clear that parseInt is not super safe. It tries to be clever by coercing, but it fails in some circumstances. One solution is to put quotes around your numbers and use the function as it was primarily intended.

However, it’s probably not very memory efficient or speedy to use strings to get around this problem, particularly if you are dealing with lots of values.

Solution 1

You can use Math functions to round or truncate numbers to form integers, such as Math.round, Math.ceil, Math.floor and Math.trunc.

My preferred solution is Math.trunc because it simply truncates everything from the decimal point, and doesn’t apply any rounding logic.

Example 1

Math.trunc(1.655)
> 1

Example 2

Math.trunc(0.000000006)
> 0

You can implement the truncation logic very simply using Math.ceil and Math.floor in cases where you cannot run ES2015 functions.

function ToInteger(v) { 
    if (v > 0) {
        return Math.floor(v);
    }
    return Math.ceil(v);
}
ToInteger(0.000000006)
> 0

Solution 2

A potentially faster method of converting a number to an integer is to use bitwise operators. Bitwise in JavaScript works by converting the operands of an operation to 32-bit signed integer representation, and then flips or shifts bits where applicable.

We can use the OR operator as below.

1.655 | 0
-> 1

1.655 gets converted to 0b00000001 when used in a bitwise operation. This is 1 in binary. That’s it. It’s done the truncation and returned the integer for us.

In order to coerce the JavaScript engine to do this, we had to provide two operands and a bitwise operator, but we didn’t want to make any changes to the left operand.

The OR operator yields a 1 for each bit column if either operand has a 1 in the corresponding column. By using 0 as the right operand, the return value in every column is the same.

00000001
   |
00000000
-> 00000001

The same behaviour can be achieved with the shift operator, >>

The drawback to this solution is that we can only use numbers in 32-bit representation. Numbers in JavaScript are 64-bit floating points, so we can represent much higher values if we wanted to.

If we attempt to convert 4000000000000000000000.1 to an integer with this method, we get an overflow.

4000000000000000000000.1 | 0
-> 2055208960

Method 1 didn’t suffer from this. If you can guarantee not to have large values (+/- 2^32), use bitwise, otherwise I recommend using Math.trunc, or perhaps Math.round if you prefer to round up/down.



comments powered by Disqus