parseInt (null, 24) === 23… czekaj, co?

226

W porządku, więc bawiłem się parsowaniem, żeby zobaczyć, jak radzi sobie z wartościami, które nie zostały jeszcze zainicjowane, i natknąłem się na ten klejnot. Poniższe dzieje się w przypadku dowolnego podstawnika 24 lub nowszego.

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

Przetestowałem to w IE, Chrome i Firefox i wszystkie alarmy są prawdziwe, więc myślę, że musi to być gdzieś w specyfikacji. Szybkie wyszukiwanie w Google nie dało mi żadnych wyników, więc mam nadzieję, że ktoś może to wyjaśnić.

Pamiętam, jak słuchałem przemówienia Crockforda typeof null === "object"z powodu niedopatrzenia, które spowodowało, że Object i Null mają w pamięci niemal identyczny typ identyfikatora lub coś podobnego, ale nie mogę teraz znaleźć tego filmu.

Wypróbuj: http://jsfiddle.net/robert/txjwP/

Edytuj korekcję: wyższa podstawa zwraca różne wyniki, 32 zwraca 785077
Edytuj 2 Z zzzzBov:[24...30]:23, 31:714695, 32:785077, 33:859935, 34:939407, 35:1023631, 36:1112745


tl; dr

Wyjaśnij, dlaczego parseInt(null, 24) === 23jest to prawdziwe stwierdzenie.

Robert
źródło
49
Jak dziwnie. JavaScript zawsze trzyma Cię na nogach.
FishBasketGordo
1
punkt danych: alert(parseInt(null, 34) === 23)wyprodukowanofalse
Stephen P
1
alert(parseInt(null,26)===23);produkuje również prawdziwe?!?!
Petar Iwanow
6
[24...30]:23, 31:714695, 32:785077, 33:859935, 34:939407, 35:1023631, 36:1112745,[37...]:NaN
zzzzBov
1
Jako dodatkową uwagę, undefinedponieważ pierwszy parametr zwraca nieparzyste wyniki dla lat 30-tych
zzzzBov

Odpowiedzi:

240

Konwertuje nullna ciąg "null"i próbuje go przekonwertować. W przypadku podstaw od 0 do 23 nie ma cyfr, które mógłby przekonwertować, więc zwraca NaN. W punkcie 24 "n"czternasta litera jest dodawana do systemu liczbowego. W dniu 31 "u"dodaje się 21. literę, a cały ciąg można dekodować. W 37 dniu nie ma już żadnego prawidłowego zestawu liczb, który można wygenerować, a NaN jest zwracany.

js> parseInt(null, 36)
1112745

>>> reduce(lambda x, y: x * 36 + y, [(string.digits + string.lowercase).index(x) for x in 'null'])
1112745
Ignacio Vazquez-Abrams
źródło
3
@Tomalak: Kto mówi, że używa toString()?
Ignacio Vazquez-Abrams,
3
@Ignacio. Myliłem się. Nie wiedziałem, że 37 odnosi się do podstawy. Przepraszam za to.
Mike Samuel,
3
@Robert, nie, byłem zdezorientowany i myślałem, że żąda czegoś innego niż to, co twierdzi. To właściwa odpowiedź. Przeprosiny dookoła.
Mike Samuel,
19
Nadal uważam, że ta odpowiedź mogłaby przydać się w przypadku niektórych odniesień. Chociaż jest to całkowicie poprawne, to tak naprawdę tylko jedno wielkie stwierdzenie ...
Lekkość ściga się na orbicie
4
@ Tomalak - sprawdź moją odpowiedź na wszystkie odniesienia. Ta odpowiedź jest poprawna (i pierwsza), więc uważam, że powinna pozostać zaakceptowana. Chociaż nigdy nie boli wyjaśniać, co dzieje się pod maską;)
David Titarenco
118

Mozilla mówi nam :

funkcja parseInt konwertuje swój pierwszy argument na ciąg , analizuje go i zwraca liczbę całkowitą lub NaN. Jeśli nie NaN, zwrócona wartość będzie dziesiętną liczbą całkowitą pierwszego argumentu wziętego jako liczba w podanej podstawce (podstawie). Na przykład podstawa 10 oznacza konwersję z liczby dziesiętnej, 8 ósemkowej, 16 szesnastkowej i tak dalej. W przypadku radic powyżej 10 litery alfabetu wskazują cyfry większe niż 9. Na przykład dla liczb szesnastkowych (podstawa 16) stosuje się litery od A do F.

W specyfikacji 15.1.2.2/1 mówi nam, że konwersja na ciąg znaków jest wykonywana przy użyciu wbudowanego ToString, który (zgodnie z 9.8) daje "null"(nie mylić z tym toString, co by dał "[object Window]"!).

Zastanówmy się parseInt("null", 24).

Oczywiście nie jest to ciąg liczbowy 24 w całości, ale „n” to: dziesiętny 23 .

Teraz parsowanie zatrzymuje się po wyciągnięciu dziesiętnego 23, ponieważ "u" nie występuje w systemie base-24:

Jeśli S zawiera dowolny znak, który nie jest cyfrą Rx, to niech Z będzie podciągiem S składającym się ze wszystkich znaków przed pierwszym takim znakiem; w przeciwnym razie niech Z będzie S. [15.1.2.2/11]

(I właśnie dlatego parseInt(null, 23)(i niższe radie) daje ci NaNraczej niż 23: "n"nie ma w systemie base-23).

Lekkość Wyścigi na orbicie
źródło
2
Jest to bardzo tragiczne zachowanie parsowania (zastanawiałem się, dlaczego nie zaprojektowano tego wyjątku w tym przypadku). Wolę używać NUMBER () zamiast tego, kiedy mogę.
Grijesh Chauhan
79

Ignacio Vazquez-Abrams ma rację, ale zobaczmy dokładnie, jak to działa ...

Od 15.1.2.2 parseInt (string , radix):

Po wywołaniu funkcji parseInt podejmowane są następujące kroki:

  • Niech inputString będzie ToString (ciąg).
  • Niech S będzie nowo utworzonym podciągiem inputString składającym się z pierwszego znaku, który nie jest StrWhiteSpaceChar i wszystkich znaków następujących po tym znaku. (Innymi słowy, usuń wiodące białe spacje).
  • Niech znak będzie 1.
  • Jeśli S nie jest puste, a pierwszy znak S jest znakiem minus -, niech znak będzie −1.
  • Jeśli S nie jest puste, a pierwszy znak S jest znakiem plus + lub znakiem minus -, usuń pierwszy znak z S.
  • Niech R = ToInt32 (podstawa).
  • Niech stripPrefix będzie prawdziwy.
  • Jeśli R ≠ 0, to a. Jeśli R <2 lub R> 36, zwróć NaN. b. Jeśli R = 16, niech stripPrefix ma wartość false.
  • W przeciwnym razie R = 0 a. Niech R = 10.
  • Jeśli stripPrefix ma wartość true, to a. Jeśli długość S wynosi co najmniej 2, a pierwsze dwa znaki S mają albo „0x”, albo „0X”, usuń pierwsze dwa znaki z S i niech R = 16.
  • Jeśli S zawiera dowolny znak, który nie jest cyfrą Rx, to niech Z będzie podciągiem S składającym się ze wszystkich znaków przed pierwszym takim znakiem; w przeciwnym razie niech Z będzie S.
  • Jeśli Z jest puste, zwróć NaN.
  • Niech matath nie będzie matematyczną liczbą całkowitą reprezentowaną przez Z w notacji Radix-R, używając liter AZ i az dla cyfr o wartościach od 10 do 35. (Jeżeli jednak R wynosi 10, a Z zawiera więcej niż 20 cyfr znaczących, każda znacząca cyfra po 20. może zostać zastąpiona cyfrą 0, zależnie od opcji implementacji; a jeśli R nie jest 2, 4, 8, 10, 16 lub 32, to matematyka może być zależnym od implementacji przybliżeniem liczby całkowitej matematycznej wartość reprezentowana przez Z w notacji radix-R).
  • Niech liczba będzie wartością liczbową dla mathInt.
  • Znak powrotu × liczba.

UWAGA parseInt może interpretować tylko wiodącą część ciągu jako wartość całkowitą; ignoruje wszelkie znaki, których nie można interpretować jako część notacji liczb całkowitych, i nie podano żadnych wskazówek, że takie znaki były ignorowane.

Istnieją tutaj dwie ważne części. Pogrubiłem ich obu. Przede wszystkim musimy dowiedzieć się, na czym polega toStringreprezentacja null. Informacje te znajdują się Table 13 — ToString Conversionsw rozdziale 9.8.0:

wprowadź opis zdjęcia tutaj

Świetnie, więc teraz wiemy, że wykonywanie toString(null)wewnętrznie daje 'null'ciąg. Świetnie, ale jak dokładnie obsługuje cyfry (znaki), które nie są poprawne w podanym podstawce?

Patrzymy wyżej 15.1.2.2i widzimy następującą uwagę:

Jeśli S zawiera dowolny znak, który nie jest cyfrą Rx, to niech Z będzie podciągiem S składającym się ze wszystkich znaków przed pierwszym takim znakiem; w przeciwnym razie niech Z będzie S.

Oznacza to, że obsługujemy wszystkie cyfry PRZED podaną podstawą i ignorujemy wszystko inne.

Zasadniczo robienie parseInt(null, 23)jest tym samym, co parseInt('null', 23). uPowoduje dwa l„s być ignorowane (mimo że są one częścią radix 23). Dlatego możemy tylko parsować n, dzięki czemu cała instrukcja jest synonimem parseInt('n', 23). :)

Tak czy inaczej, świetne pytanie!

David Titarenco
źródło
33
parseInt( null, 24 ) === 23

Jest równa

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

co jest równoważne z

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

Cyfry dla podstawy 24 to 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, ..., n.

Specyfikacja językowa mówi

  1. Jeśli S zawiera dowolny znak, który nie jest cyfrą Rx, to niech Z będzie podciągiem S składającym się ze wszystkich znaków przed pierwszym takim znakiem; w przeciwnym razie niech Z będzie S.

która jest częścią, która zapewnia, że ​​literały całkowite w stylu C, takie jak 15Lparsowanie poprawnie, więc powyższe jest równoważne

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

"n" to 23-ta litera powyższej listy cyfr.

CO BYŁO DO OKAZANIA

Mike Samuel
źródło
16

Chyba nullzostaje przekształcony w ciąg "null". Tak więc nfaktycznie znajduje się 23w „base24” (to samo w „base25” +), ujest niepoprawne w „base24”, więc reszta ciągu nullzostanie zignorowana. Właśnie dlatego wyświetla dane wyjściowe, 23ustanie się ważny w 'base31'.

Floern
źródło
7

parseInt używa reprezentacji alfanumerycznej, następnie w podstawie 24 „n” jest poprawny, ale „u” jest niepoprawnym znakiem, a następnie parsuje tylko parsuje wartość „n” ....

parseInt("n",24) -> 23

jako przykład spróbuj tego:

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

Wynik to „3”.

fdaines
źródło