javascript - parseInt(null, 24) === 23... vänta, vad?


Okej, så jag höll på och lekte med parseInt för att se hur det hanterar värden som ännu inte är initialiserade och jag snubblade över den här pärlan. Nedanstående händer för varje radix 24 eller högre.

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

Jag testade det i IE, Chrome och Firefox och de alla varnar för true, så jag tänker att det måste finnas någonstans i specifikationen. En snabb Google-sökning gav mig inga resultat, så här är jag, hoppas att någon kan förklara.

Jag minns att jag lyssnade på ett tal av Crockford där han sa typeof null === "object" på grund av en förbiseende som gjorde att Object och Null hade en nästan identisk typidentifierare i minnet eller något i den stilen, men jag kan inte hitta den videon nu.

Prova det: http://jsfiddle.net/robert/txjwP/

Redigera Korrigering: en högre bas ger olika resultat, 32 ger 785077
Redigera 2 Från zzzzBov: [24...30]:23, 31:714695, 32:785077, 33:859935, 34:939407, 35:1023631, 36:1112745

sammanfattning

Förklara varför parseInt(null, 24) === 23 är ett sant påstående.

Svar #1

Det konverterar null till strängen "null" och försöker konvertera den. För baser 0 till 23 finns det inga siffror som kan konverteras, så det returnerar NaN. Vid 24 läggs "n", den 14:e bokstaven, till talssystemet. Vid 31 läggs "u", den 21:a bokstaven, till och hela strängen kan avkodas. Vid 37 och därefter finns det inte längre några giltiga sifferuppsättningar som kan genereras och NaN returneras.

js> parseInt(null, 36)
1112745

>>> reduce(lambda x, y: x * 36 + y, [(string.digits + string.lowercase).index(x) for x in 'null'])
1112745

Svar #2

Mozilla berättar för oss:

funktionen parseInt konverterar sitt första argument till en sträng, analyserar det och returnerar ett heltal eller NaN. Om det inte är NaN, kommer det returnerade värdet att vara det decimala heltalsvärdet av det första argumentet taget som ett tal i den angivna basen (radix). Till exempel indikerar en radix på 10 att konvertera från ett decimaltal, 8 oktal, 16 hexadecimal, och så vidare. För radixer över 10 indikerar alfabetets bokstäver siffror större än 9. Till exempel, för hexadecimala tal (bas 16), används A till F.

I specifikationen berättar 15.1.2.2/1 för oss att omvandlingen till sträng utförs med den inbyggda ToString, som (enligt 9.8) ger "null" (att inte förväxla med toString, som skulle ge "[object Window]"!).

Så, låt oss överväga parseInt("null", 24).

Självklart är detta inte en helt bas-24 numerisk sträng, men "n" är det: det är decimal 23.

Nu slutar parsningen efter att decimalen 23 plockats ut, eftersom "u" inte finns i bas-24-systemet:

Om S innehåller någon tecken som inte är en radix-R-siffra, låt Z vara delsträngen av S som består av alla tecken före det första sådana tecken; annars, låt Z vara S. [15.1.2.2/11]

(Och detta är anledningen till att parseInt(null, 23) (och lägre baser) ger dig NaN istället för 23: "n" finns inte i bas-23-systemet.)

Svar #3

Ignacio Vazquez-Abrams har rätt, men låt oss se exakt hur det fungerar...

Från 15.1.2.2 parseInt (string , radix):

När funktionen parseInt anropas, vidtas följande steg:

  • Låt inputString vara ToString(string).
  • Låt S vara en nyss skapad delsträng av inputString bestående av det första tecknet som inte är ett StrWhiteSpaceChar och alla tecken som följer efter det tecknet. (Med andra ord, ta bort ledande blanksteg.)
  • Låt tecknet vara 1.
  • Om S inte är tomt och det första tecknet i S är ett minustecken -, låt då tecknet vara −1.
  • Om S inte är tomt och det första tecknet i S är ett plustecken + eller ett minustecken -, ta då bort det första tecknet från S.
  • Låt R=ToInt32(radix).
  • Låt stripPrefix vara sant.
  • Om R ≠ 0, då a. Om R < 2 eller R > 36, returnera NaN. b. Om R ≠ 16, låt stripPrefix vara falsk.
  • Annars, R=0 a. Låt R=10.
  • Om stripPrefix är sant, då a. Om längden på S är minst 2 och de första två tecknen i S är antingen "0x" eller "0X", ta bort de första två tecknen från S och låt R=16.
  • Om S innehåller någon tecken som inte är en radix-R-siffra, låt Z vara delsträngen av S som består av alla tecken före det första sådana tecknet; annars, låt Z vara S.
  • Om Z är tomt, returnera NaN.
  • Låt mathInt vara det matematiska heltalsvärdet som representeras av Z i bas-R-notation, med bokstäverna A-Z och a-z som siffror med värden 10 till 35. (Om R är 10 och Z innehåller mer än 20 signifikanta siffror får varje signifikant siffra efter den 20:e ersättas med en 0-siffra, enligt implementeringens val; och om R inte är 2, 4, 8, 10, 16 eller 32, kan mathInt vara en implementeringsberoende approximation av det matematiska heltalsvärdet som representeras av Z i bas-R-notation.)
  • Låt number vara Number-värdet för mathInt.
  • Returnera tecken × nummer.

OBS parseInt kan endast tolka en ledande del av strängen som ett heltalsvärde; den ignorerar alla tecken som inte kan tolkas som en del av ett heltal, och ingen indikation ges på att några sådana tecken ignorerades.

Det finns två viktiga delar här. Jag har fetmarkerat båda. Så först och främst måste vi ta reda på vad toString-representationen av null är. Vi behöver titta på Tabell 13 — ToString-konversioner i avsnitt 9.8.0 för den informationen:

Bra, så nu vet vi att att göra toString(null) internt ger en 'null'-sträng. Bra, men hur hanterar det exakt siffror (tecken) som inte är giltiga inom den angivna basen?

Vi tittar ovan på 15.1.2.2 och ser följande kommentar:

Om S innehåller någon tecken som inte är en radix-R siffra, låt Z vara delsträngen av S som består av alla tecken före det första sådana tecken; annars, låt Z vara S.

Det betyder att vi hanterar alla siffror FÖRE den angivna basen och ignorerar allt annat.

I princip är att göra parseInt(null, 23) samma sak som parseInt('null', 23). u gör att de två l-tecknen ignoreras (även om de ÄR en del av basen 23). Därför kan vi endast tolka n, vilket gör hela uttrycket synonymt med parseInt('n', 23). :)

Hur som helst, bra fråga!

Svar #4

parseInt( null, 24 ) === 23

Är ekvivalent med

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

vilket motsvarar

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

Siffrorna för bas 24 är 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, ..., n.

Språkspecifikationen säger

  1. Om S innehåller någon tecken som inte är en radix-R-siffra, låt då Z vara delsträngen av S som består av alla tecken före det första sådana tecknet; annars, låt Z vara S.

vilken är delen som säkerställer att C-stil heltalsliteraler som 15L tolkas korrekt, så ovanstående är ekvivalent med

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

"n" är den 23:e bokstaven i siffralistan ovan.

Kv.V.D.

Svar #5

Jag antar att null konverteras till en sträng "null". Så n är faktiskt 23 i 'base24' (samma i 'base25'+), u är ogiltig i 'base24' så resten av strängen null kommer att ignoreras. Det är därför den skriver ut 23 tills u blir giltig i 'base31'.