2013-01-29

parseInt() doesn’t always correctly convert to integer

In JavaScript, all numbers are floating point. Integers are floating point numbers without a fraction. Converting a number n to an integer means finding the integer that is “closest” to n (where “closest” is a matter of definition). You might think that parseInt() performs this task, but it doesn’t. At least, not always.

How parseInt() works

parseInt() has the following signature:
    parseInt(value, radix?)
It converts value to string, ignores leading whitespace and then parses as many consecutive integer digits as it can find.

The radix

If radix is missing then it is assumed to be 10 except if value begins with '0x' or '0X', in which case radix is set to 16 (hexadecimal).
    > parseInt('0xA')
    10
If radix is already 16 then the hexadecimal prefix is optional.
    > parseInt('0xA', 16)
    10
    > parseInt('A', 16)
    10
So far we have described the behavior of parseInt() according to the ECMAScript specification. Additionally, some engines set the radix to 8 if the integer starts with a leading zero:
    > parseInt('010')
    8
    > parseInt('0109')  // ignores digits ≥ 8
    8
Thus, it is best to always explicitly specify the radix.

Examples

A few examples:
    > parseInt('   12', 10)
    12
    > parseInt('12**', 10)
    12
    > parseInt('12.34', 10)
    12
    > parseInt(12.34, 10)
    12
The last example gives us hope that we might be able to use parseInt() for converting numbers to integers. Alas, here is an example where the conversion is incorrect:
    > parseInt(1000000000000000000000.5, 10)
    1
Explanation: The argument is first converted to string.
    > String(1000000000000000000000.5)
    '1e+21'
parseInt doesn’t consider “e” to be an integer digit and thus stops parsing after the 1. Another example:
    > parseInt(0.0000008, 10)
    8
    > String(0.0000008)
    '8e-7'
This clearly limits the usefulness of parseInt(). Note, however, that exponential notation with positive exponents starts at ±1e+21 [1]:
    > String(1e+20)
    '100000000000000000000'
    > String(1e+21)
    '1e+21'
This is well beyond the range [−253, 253] of contiguous integers in JavaScript [2].

Alternatives to parseInt()

The blog post “Integers and shift operators in JavaScript” mentions several alternatives for converting numbers to integer. One good option is Math.round(), which always rounds to the “closest” integer:
    > Math.round(3.2)
    3
    > Math.round(3.5)
    4
    > Math.round(3.8)
    4

    > Math.round(-3.2)
    -3
    > Math.round(-3.5)
    -3
    > Math.round(-3.8)
    -4
Note that 4 is considered closest to 3.5, while −3 is considered closest to -3.5.

Another good option is the following function, an implementation of the ToInteger() operation from the ECMAScript specification:

    function ToInteger(x) {
        x = Number(x);
        return x < 0 ? Math.ceil(x) : Math.floor(x);
    }
This function simply removes the fraction. Examples:
    > ToInteger(3.2)
    3
    > ToInteger(3.5)
    3
    > ToInteger(3.8)
    3
    > ToInteger(-3.2)
    -3
    > ToInteger(-3.5)
    -3
    > ToInteger(-3.8)
    -3

Further reading

This blog post is part of a series on numbers in JavaScript.
  1. Displaying numbers in JavaScript
  2. How numbers are encoded in JavaScript

No comments: