Allen Wirfs-Brock recently discussed the impedance mismatch when functions accepting optional arguments are incompatibly combined, considering in particular combining
Array.prototype.map. In doing so he makes this comment:
The most common usage of parseInt passes only a single argument
Code-quality systems like JSLint routinely warn about
parseInt without an explicit radix. Most uses might well pass only a single argument, but I could easily imagine this going the other way.
This raises an interesting question: why do lints warn about using
parseInt without a radix?
parseInt and radixes
parseInt tries to be helpful when called without an explicitly specified radix. It attempts to guess a suitable radix:
assertEq(parseInt("+17"), 17); assertEq(parseInt("42"), 42); assertEq(parseInt("-0x42"), -66); // assertEq(parseInt("0755"), ???); // we'll get back to this
If the string (after optional leading whitespace and
-) starts with a non-zero decimal digit, it’s parsed as decimal. But if the string begins with
0, things get wacky. If the next character is
X, the number is parsed in base-16: hexadecimal. Last, if the next character isn’t
X…hold that thought. I’ll return to it in a moment.
Thus the behavior of
parseInt without a radix depends not just on the numeric contents of the string but also upon its internal structure, entirely separate from its contents. This alone is reason enough to always specify a radix: specify a radix
2 ≤ r ≤ 36 and it will be used, no guessing, no uncertain behavior in the face of varying strings. (Although, to be sure, there’s still a very slight wrinkle: if
r === 16 and your string begins with
0X, they’ll be skipped when determining the integer to return. But this is a pretty far-out edge case where you might want to parse a hexadecimal string without a prefix and would also want to process one with a prefix as just
But wait! There’s more
Beyond cuteness lies another concern. Let’s return to the leading-zero-but-not-hexadecimal case:
7 × 8 × 8 + 5 × 8 + 5 === 493.
On the other hand, as I noted in Mozilla’s ES5 strict mode documentation, there’s some evidence that people use leading zeroes as alignment devices, thinking they have no semantic effect. So maybe leading zero is decimal instead.
The wonderful thing about standards is that there are so many of them to choose from
According to ES3, a leading zero with no explicit radix is either decimal or, if octal extensions have been implemented, octal. So what happens depends on who wrote the ES3 implementation, and what choice they made. But what if it’s an ES5 implementation? ES5 explicitly forbids octal and says this is interpreted as decimal. Therefore,
755 in bog-standard ES3 implementations,
493 in ES3 implementations which have implemented octal extensions, and
755 in conforming ES5 implementations. Isn’t it great?
What do browsers actually do?
On the web everyone implements the octal extensions, so you’ll have to look hard to find an ES3 browser that doesn’t make
parseInt("0755") === 493. But ES3 is old and busted, and ES5 is the new hotness. What do ES5 implementations do, especially as the change in ES5 isn’t backwards-compatible?
Surprisingly browsers aren’t all playing chicken here, waiting to see that they can change without breaking sites. On this particular point IE9 leads the way (in standards mode code only), implementing
parseInt("0755") === 755 despite having implemented
parseInt("0755") === 493 in the past. Before I saw IE9 implemented this (although I hasten to note they have not shipped a release with it yet), I expected no browser would implement it due to the possibility of breaking sites. After seeing IE9’s example, I’m less certain. Hopefully their experience will shed light on the decision for the other browser vendors.
Precise details of browser and specification inconsistencies aside, the point remains that
parseInt(str) tries to be cute when parsing
str. That cuteness can make
parseInt(str) unpredictable if inputs vary sufficiently. Avoid edge-case bugs and use
parseInt(str, radix) instead.