parseInt(null, 24) === 23... wait, what?
Asked Answered
G

6

230

Alright, so I was messing around with parseInt to see how it handles values not yet initialized and I stumbled upon this gem. The below happens for any radix 24 or above.

parseInt(null, 24) === 23 // evaluates to true

I tested it in IE, Chrome and Firefox and they all alert true, so I'm thinking it must be in the specification somewhere. A quick Google search didn't give me any results so here I am, hoping someone can explain.

I remember listening to a Crockford speech where he was saying typeof null === "object" because of an oversight causing Object and Null to have a near identical type identifier in memory or something along those lines, but I can't find that video now.

Try it: http://jsfiddle.net/robert/txjwP/

Edit Correction: a higher radix returns different results, 32 returns 785077
Edit 2 From zzzzBov: [24...30]:23, 31:714695, 32:785077, 33:859935, 34:939407, 35:1023631, 36:1112745


tl;dr

Explain why parseInt(null, 24) === 23 is a true statement.

Gymnosophist answered 23/6, 2011 at 19:48 Comment(11)
How quirky. JavaScript always keeps you on your toes.Leavitt
@Tomalak Geret'kal: Why does parseInt(null, 24) yield 23, surely that's not supposed to?Gymnosophist
I believe that the question is, "Why does parseInt(null, 24) equal 23 in this case?"Leavitt
data point: alert(parseInt(null, 34) === 23) produced falseCami
@Stephen P: Right, my bad, forgot, that produces 785077, let me updateGymnosophist
alert(parseInt(null,26)===23); also produces true ?!?!Exegesis
[24...30]:23, 31:714695, 32:785077, 33:859935, 34:939407, 35:1023631, 36:1112745, [37...]:NaNTrencher
zzzzBov: parseInt won't handle an radix above 36, but I'll put the other stuff in the question for easier reference.Gymnosophist
@Robert, i forgot that parseInt was limited to 36.Trencher
As an additional note, undefined as the first parameter returns odd results for the 30'sTrencher
@zzzzBov: It shouldn't. Well, it depends how you define "odd".Nonrecognition
U
243

It's converting null to the string "null" and trying to convert it. For radixes 0 through 23, there are no numerals it can convert, so it returns NaN. At 24, "n", the 14th letter, is added to the numeral system. At 31, "u", the 21st letter, is added and the entire string can be decoded. At 37 on there is no longer any valid numeral set that can be generated and NaN is returned.

js> parseInt(null, 36)
1112745

>>> reduce(lambda x, y: x * 36 + y, [(string.digits + string.lowercase).index(x) for x in 'null'])
1112745
Uphemia answered 23/6, 2011 at 20:3 Comment(18)
i hadn't even thought about casting null to a string. i'm not aware of any other function that would do that. Good call though, makes perfect sense.Trencher
Not true. "u" cannot be decoded in base 14 and it trips clause 11 of 15.1.2.2 " If S contains any character that is not a radix-R digit, then let Z be the substring of S consisting of all characters before the first such character; otherwise, let Z be S." so only the "n" part of "null" is ever decoded in base 24.Horacehoracio
No it's not. toString(null) is "[object Window]" - jsfiddle.net/Lf8Yd.Nonrecognition
@Tomalak: Who says that it's using toString()?Uphemia
It looks like it's doing something like "" + valuePassed as undefined starts returning an integer as soon as u as a valid number, at base 31Gymnosophist
@Ignacio, it's not using toString. It uses the spec builtin ToString which is quite different : "When the parseInt function is called, the following steps are taken: 1. Let inputString be ToString(string)."Horacehoracio
@Ignacio: 15.1.2.2/1 in the spec.Nonrecognition
@Mike: Oh, what is ToString then?Nonrecognition
@Tomalak, From section 9.8 of the language spec: "The abstract operation ToString converts its argument to a value of type String according to Table 13:"Horacehoracio
@Mike: You're right; see 9.8. @Ignacio: Sorry, downvote removed :)Nonrecognition
@Mike Samuel: It makes sense to me, I'm still here though, if it's wrong whats the right answer?Gymnosophist
@Ignacio. Actually, I was wrong. I didn't realize 37 was referring to a radix. Sorry about that.Horacehoracio
@Robert, no I was confused and thought he was claiming something other than what he was claiming. It's the right answer. Apologies all around.Horacehoracio
@Mike: 15.1.2.2/8 "If R < 2 or R > 36, then return NaN." ;)Nonrecognition
@Tomalak, yep. Strictly speaking, there's a ToInt32 in there (es5.github.com/#x15.1 step 6), so parseInt('15', 4294967306) is a counterexample but now I'm splitting hairs :)Horacehoracio
I still think that this answer could do with some references. Although it's entirely correct, it's really just one big assertion...Nonrecognition
@Tomalak - check my answer for all the references. This answer is the correct (and first) one so I think it should remain the accepted one. Although it never hurts to explain what goes on under the hood ;)Caylacaylor
@David: First by five seconds. I don't really think that that's important. Note that I'm not suggesting it should not be the accepted one.Nonrecognition
N
120

Mozilla tells us:

function parseInt converts its first argument to a string, parses it, and returns an integer or NaN. If not NaN, the returned value will be the decimal integer representation of the first argument taken as a number in the specified radix (base). For example, a radix of 10 indicates to convert from a decimal number, 8 octal, 16 hexadecimal, and so on. For radices above 10, the letters of the alphabet indicate numerals greater than 9. For example, for hexadecimal numbers (base 16), A through F are used.

In the spec, 15.1.2.2/1 tells us that the conversion to string is performed using the built-in ToString, which (as per 9.8) yields "null" (not to be confused with toString, which would yield "[object Window]"!).

So, let's consider parseInt("null", 24).

Of course, this isn't a base-24 numeric string in entirety, but "n" is: it's decimal 23.

Now, parsing stops after the decimal 23 is pulled out, because "u" isn't found in the base-24 system:

If S contains any character that is not a radix-R digit, then let Z be the substring of S consisting of all characters before the first such character; otherwise, let Z be S. [15.1.2.2/11]

(And this is why parseInt(null, 23) (and lower radices) gives you NaN rather than 23: "n" is not in the base-23 system.)

Nonrecognition answered 23/6, 2011 at 20:3 Comment(1)
This is very tragic behavior of parseInt (I was thinking why it was not design to though exception in these case). I would prefer to use NUMBER() instead when I can.Braunstein
C
81

Ignacio Vazquez-Abrams is correct, but lets see exactly how it works...

From 15.1.2.2 parseInt (string , radix):

When the parseInt function is called, the following steps are taken:

  • Let inputString be ToString(string).
  • Let S be a newly created substring of inputString consisting of the first character that is not a StrWhiteSpaceChar and all characters following that character. (In other words, remove leading white space.)
  • Let sign be 1.
  • If S is not empty and the first character of S is a minus sign -, let sign be −1.
  • If S is not empty and the first character of S is a plus sign + or a minus sign -, then remove the first character from S.
  • Let R = ToInt32(radix).
  • Let stripPrefix be true.
  • If R ≠ 0, then a. If R < 2 or R > 36, then return NaN. b. If R ≠ 16, let stripPrefix be false.
  • Else, R = 0 a. Let R = 10.
  • If stripPrefix is true, then a. If the length of S is at least 2 and the first two characters of S are either “0x” or “0X”, then remove the first two characters from S and let R = 16.
  • If S contains any character that is not a radix-R digit, then let Z be the substring of S consisting of all characters before the first such character; otherwise, let Z be S.
  • If Z is empty, return NaN.
  • Let mathInt be the mathematical integer value that is represented by Z in radix-R notation, using the letters A-Z and a-z for digits with values 10 through 35. (However, if R is 10 and Z contains more than 20 significant digits, every significant digit after the 20th may be replaced by a 0 digit, at the option of the implementation; and if R is not 2, 4, 8, 10, 16, or 32, then mathInt may be an implementation-dependent approximation to the mathematical integer value that is represented by Z in radix-R notation.)
  • Let number be the Number value for mathInt.
  • Return sign × number.

NOTE parseInt may interpret only a leading portion of string as an integer value; it ignores any characters that cannot be interpreted as part of the notation of an integer, and no indication is given that any such characters were ignored.

There are two important parts here. I bolded both of them. So first of all, we have to find out what the toString representation of null is. We need to look at Table 13 — ToString Conversions in section 9.8.0 for that information:

enter image description here

Great, so now we know that doing toString(null) internally yields a 'null' string. Great, but how exactly does it handle digits (characters) that aren't valid within the radix provided?

We look above to 15.1.2.2 and we see the following remark:

If S contains any character that is not a radix-R digit, then let Z be the substring of S consisting of all characters before the first such character; otherwise, let Z be S.

That means that we handle all digits PRIOR to the specified radix and ignore everything else.

Basically, doing parseInt(null, 23) is the same thing as parseInt('null', 23). The u causes the two l's to be ignored (even though they ARE part of the radix 23). Therefore, we only can only parse n, making the entire statement synonymous to parseInt('n', 23). :)

Either way, great question!

Caylacaylor answered 23/6, 2011 at 20:17 Comment(0)
H
34
parseInt( null, 24 ) === 23

Is equivalent to

parseInt( String(null), 24 ) === 23

which is equivalent to

parseInt( "null", 24 ) === 23

The digits for base 24 are 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, ..., n.

The language spec says

  1. If S contains any character that is not a radix-R digit, then let Z be the substring of S consisting of all characters before the first such character; otherwise, let Z be S.

which is the part that ensures that C-style integer literals like 15L parse properly, so the above is equivalent to

parseInt( "n", 24 ) === 23

"n" is the 23-rd letter of the digit list above.

Q.E.D.

Horacehoracio answered 23/6, 2011 at 20:4 Comment(0)
E
16

I guess null gets converted to a string "null". So n is actually 23 in 'base24' (same in 'base25'+), u is invalid in 'base24' so the rest of the string null will be ignored. That's why it outputs 23 until u will become valid in 'base31'.

Eyecup answered 23/6, 2011 at 20:3 Comment(0)
U
7

parseInt uses alphanumeric representation, then in base-24 "n" is valid, but "u" is invalid character, then parseInt only parses the value "n"....

parseInt("n",24) -> 23

as an example, try with this:

alert(parseInt("3x", 24))

The result will be "3".

Uninstructed answered 23/6, 2011 at 20:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.