194

I'm reading this but I'm confused by what is written in the parseInt with a radix argument chapter

table of parseInt(_, 3) outcomes

Why is it that parseInt(8, 3)NaN and parseInt(16, 3)1?

AFAIK 8 and 16 are not base-3 numbers, so parseInt(16, 3) should return NaN too

the first ten base-3 natural numbers

senshin
  • 10,022
  • 7
  • 46
  • 59
Devid Farinelli
  • 7,514
  • 9
  • 42
  • 73
  • 4
    Yet another issue that would have been solved by static typing (or at least not implicitly converting integers to strings) :P – Navin Aug 26 '16 at 14:16
  • 4
    @Navin This has nothing to do with static versus dynamic typing (as you note yourself). The problem here is weak as opposed to strong typing. – Sven Marnach Aug 26 '16 at 21:06
  • 12
    When I saw the title of this question I thought to myself, "it's probably because loljavascript". Seeing the answers I judge my instinct to have been basically correct. – Ben Millwood Aug 27 '16 at 05:45

3 Answers3

377

This is something people trip over all the time, even when they know about it. :-) You're seeing this for the same reason parseInt("1abc") returns 1: parseInt stops at the first invalid character and returns whatever it has at that point. If there are no valid characters to parse, it returns NaN.

parseInt(8, 3) means "parse "8" in base 3" (note that it converts the number 8 to a string; details in the spec). But in base 3, the single-digit numbers are just 0, 1, and 2. It's like asking it to parse "9" in octal. Since there were no valid characters, you got NaN.

parseInt(16, 3) is asking it to parse "16" in base 3. Since it can parse the 1, it does, and then it stops at the 6 because it can't parse it. So it returns 1.


Since this question is getting a lot of attention and might rank highly in search results, here's a rundown of options for converting strings to numbers in JavaScript, with their various idiosyncracies and applications (lifted from another answer of mine here on SO):

  • parseInt(str[, radix]) - Converts as much of the beginning of the string as it can into a whole (integer) number, ignoring extra characters at the end. So parseInt("10x") is 10; the x is ignored. Supports an optional radix (number base) argument, so parseInt("15", 16) is 21 (15 in hex). If there's no radix, assumes decimal unless the string starts with 0x (or 0X), in which case it skips those and assumes hex. (Some browsers used to treat strings starting with 0 as octal; that behavior was never specified, and was specifically disallowed in the ES5 specification.) Returns NaN if no parseable digits are found.

  • parseFloat(str) - Like parseInt, but does floating-point numbers and only supports decimal. Again extra characters on the string are ignored, so parseFloat("10.5x") is 10.5 (the x is ignored). As only decimal is supported, parseFloat("0x15") is 0 (because parsing ends at the x). Returns NaN if no parseable digits are found.

  • Unary +, e.g. +str - (E.g., implicit conversion) Converts the entire string to a number using floating point and JavaScript's standard number notation (just digits and a decimal point = decimal; 0x prefix = hex; 0o prefix = octal [ES2015+]; some implementations extend it to treat a leading 0 as octal, but not in strict mode). +"10x" is NaN because the x is not ignored. +"10" is 10, +"10.5" is 10.5, +"0x15" is 21, +"0o10" is 8 [ES2015+]. Has a gotcha: +"" is 0, not NaN as you might expect.

  • Number(str) - Exactly like implicit conversion (e.g., like the unary + above), but slower on some implementations. (Not that it's likely to matter.)

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 8
    So `parseInt` first uses `toString` on the first argument? That would make sense. – evolutionxbox Aug 25 '16 at 13:51
  • 16
    @evolutionxbox: Yup, it's the first step of the `parseInt` algorithm: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-parseint-string-radix – T.J. Crowder Aug 25 '16 at 13:52
  • 5
    I suppose `123e-2` gives `1` since it turns into `1.23` first, and then parsing stops at the decimal point? – ilkkachu Aug 25 '16 at 20:16
  • 1
    @edc65, sure, but just running it doesn't tell me how it works behind the scenes. That came to mind because the table in the question shows a `1`, which I took as granted, even though it seemed it should be `5` or 12 in base 3. Which is if course what the result was, so I'll just guess the value in the table wasn't made with that representation of the number after all. – ilkkachu Aug 25 '16 at 21:13
  • 6
    "This is something people trip over all the time, even when they know about it" -> am I the only one that thinks this should be a bug? Doing the same in Java for example will give you a `NumberFormatException` each time. – Wim Deblauwe Aug 26 '16 at 14:14
  • @evolutionxbox No, it doesn't make sense. Maybe it explains what we are seeing here, but behaving like that doesn't make sense. – Sven Marnach Aug 26 '16 at 21:08
  • @SvenMarnach The site linked in the question says: "if its argument is of numeric type it will first be converted into a string and then parsed as a number" – Devid Farinelli Aug 26 '16 at 23:38
  • 1
    @DevidFarinelli: I don't think Sven is doubting the fact that the function behaves as you described. Just whether it makes any sense that it does so :) – Ben Millwood Aug 27 '16 at 05:11
  • 4
    @SvenMarnach: *That* part of `parseInt` (coercing the first argument to string) makes sense. The purpose of `parseInt` is to *parse* a string to a whole number. So if you give it something that isn't a string, getting the string representation of it to start with makes sense. What it does *after* that is a whole 'nother story... – T.J. Crowder Aug 27 '16 at 06:52
  • 1
    @Wim No it's not a bug and you certainly couldn't fix it without breaking millions of existing programs. And honestly, if you started filing bug reports for every single stupid decision in Javascript's language specification you'd be at it for a very, very, very long time. It's at least easy to work around it. – Voo Aug 27 '16 at 08:08
54

For the same reason that

>> parseInt('1foobar',3)
<- 1

In the doc, parseInt takes a string. And

If string is not a string, then it is converted to a string

So 16, 8, or '1foobar' is first converted to string.

Then

If parseInt encounters a character that is not a numeral in the specified radix, it ignores it and all succeeding characters

Meaning it converts up to where it can. The 6, 8, and foobar are ignored, and only what is before is converted. If there is nothing, NaN is returned.

njzk2
  • 38,969
  • 7
  • 69
  • 107
0
/***** Radix 3: Allowed numbers are [0,1,2] ********/
parseInt(4, 3); // NaN - We can't represent 4 using radix 3 [allowed - 0,1,2]

parseInt(3, 3); // NaN - We can't represent 3 using radix 3 [allowed - 0,1,2]

parseInt(2, 3); // 2   - yes we can !

parseInt(8, 3); // NaN - We can't represent 8 using radix 3 [allowed - 0,1,2]

parseInt(16, 3); // 1  
//'16' => '1' (6 ignored because it not in [0,1,2])    

/***** Radix 16: Allowed numbers/characters are [0-9,A-F] *****/ 
parseInt('FOX9', 16); // 15  
//'FOX9' => 'F' => 15 (decimal value of 'F')
// all characters from 'O' to end will be ignored once it encounters the out of range'O'
// 'O' it is NOT in [0-9,A-F]

Some more examples:

parseInt('45', 13); // 57
// both 4 and 5 are allowed in Radix is 13 [0-9,A-C]

parseInt('1011', 2); // 11 (decimal NOT binary)

parseInt(7,8); // 7
// '7' => 7 in radix 8 [0 - 7]

parseInt(786,8); // 7 
// '78' => '7' => 7 (8 & next any numbers are ignored bcos 8 is NOT in [0-7])

parseInt(76,8); // 62 
// Both 7 & 6 are allowed '76' base 8 decimal conversion is 62 base 10 
SridharKritha
  • 8,481
  • 2
  • 52
  • 43