parseInt (nulo, 24) === 23 ... espera, ¿qué?

226

Muy bien, así que estaba jugando con parseInt para ver cómo maneja los valores aún no inicializados y me topé con esta gema. Lo siguiente sucede para cualquier raíz 24 o superior.

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

Lo probé en IE, Chrome y Firefox y todos alertan de verdad, así que creo que debe estar en alguna parte de la especificación. Una búsqueda rápida en Google no me dio ningún resultado, así que aquí estoy, esperando que alguien pueda explicarlo.

Recuerdo haber escuchado un discurso de Crockford en el que estaba diciendo typeof null === "object"debido a un descuido que hizo que Object y Null tuvieran un identificador de tipo casi idéntico en la memoria o algo por el estilo, pero ahora no puedo encontrar ese video.

Pruébalo: http://jsfiddle.net/robert/txjwP/

Editar corrección: una raíz más alta devuelve resultados diferentes, 32 devuelve 785077
Edición 2 de zzzzBov:[24...30]:23, 31:714695, 32:785077, 33:859935, 34:939407, 35:1023631, 36:1112745


tl; dr

Explica por qué parseInt(null, 24) === 23es una declaración verdadera.

Robert
fuente
49
Que peculiar. JavaScript siempre te mantiene alerta.
FishBasketGordo
1
punto de datos: alert(parseInt(null, 34) === 23)producidofalse
Stephen P
1
alert(parseInt(null,26)===23);también produce verdadero?!?!
Petar Ivanov
66
[24...30]:23, 31:714695, 32:785077, 33:859935, 34:939407, 35:1023631, 36:1112745,[37...]:NaN
zzzzBov
1
Como nota adicional, undefinedcomo el primer parámetro devuelve resultados impares para los años 30
zzzzBov

Respuestas:

240

Se está convirtiendo nulla la cadena "null"e intentando convertirla. Para radixes del 0 al 23, no hay números que pueda convertir, por lo que devuelve NaN. A los 24, "n"se agrega la 14a letra al sistema de numeración. A los 31, "u"se agrega la letra 21 y se puede decodificar toda la cadena. A los 37 años ya no hay ningún conjunto de números válido que pueda generarse y se devuelve NaN.

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
fuente
3
@Tomalak: ¿Quién dice que está usando toString()?
Ignacio Vazquez-Abrams
3
@Ignacio. En realidad, estaba equivocado. No me di cuenta de que 37 se refería a una raíz. Lo siento por eso.
Mike Samuel
3
@Robert, no, estaba confundido y pensé que estaba reclamando algo más que lo que estaba reclamando. Es la respuesta correcta. Disculpas por todos lados.
Mike Samuel
19
Todavía creo que esta respuesta podría tener algunas referencias. Aunque es completamente correcto, en realidad es solo una gran afirmación ...
ligereza corre en órbita el
44
@Tomalak: compruebe mi respuesta para ver todas las referencias. Esta respuesta es la correcta (y la primera), así que creo que debería seguir siendo la aceptada. Aunque nunca está de más explicar lo que sucede debajo del capó;)
David Titarenco
118

Mozilla nos dice :

La función parseInt convierte su primer argumento en una cadena , la analiza y devuelve un entero o NaN. Si no es NaN, el valor devuelto será la representación entera decimal del primer argumento tomado como un número en la raíz (base) especificada. Por ejemplo, una raíz de 10 indica convertir de un número decimal, 8 octal, 16 hexadecimales, etc. Para radios superiores a 10, las letras del alfabeto indican números mayores que 9. Por ejemplo, para números hexadecimales (base 16), se utilizan A a F.

En la especificación , 15.1.2.2/1 nos dice que la conversión a cadena se realiza utilizando el incorporado ToString, que (según 9.8) produce "null"(no debe confundirse con toString, ¡lo que produciría "[object Window]"!).

Entonces, consideremos parseInt("null", 24).

Por supuesto, esta no es una cadena numérica de base 24 en su totalidad, pero "n" es: es 23 decimal .

Ahora, el análisis se detiene después de extraer el decimal 23, porque "u" no se encuentra en el sistema base-24:

Si S contiene algún carácter que no sea un dígito radix-R, entonces Z sea la subcadena de S que consta de todos los caracteres antes del primer carácter; de lo contrario, que Z sea S. [15.1.2.2/11]

(Y esta es la razón por la cual parseInt(null, 23)(y los radios más bajos) le da en NaNlugar de 23: "n"no está en el sistema de base 23).

Carreras de ligereza en órbita
fuente
2
Este es un comportamiento muy trágico de parseInt (estaba pensando por qué no fue diseñado para pasar por una excepción en este caso). Preferiría usar NUMBER () en su lugar cuando pueda.
Grijesh Chauhan
79

Ignacio Vazquez-Abrams es correcto, pero veamos exactamente cómo funciona ...

De 15.1.2.2 parseInt (string , radix):

Cuando se llama a la función parseInt, se toman los siguientes pasos:

  • Deje que inputString sea ToString (cadena).
  • Sea S una nueva subcadena de inputString que consta del primer carácter que no es un StrWhiteSpaceChar y todos los caracteres que siguen a ese carácter. (En otras palabras, elimine los espacios en blanco iniciales).
  • Let sign be 1.
  • Si S no está vacío y el primer carácter de S es un signo menos, deje que el signo sea −1.
  • Si S no está vacío y el primer carácter de S es un signo más + o un signo menos -, elimine el primer carácter de S.
  • Deje R = ToInt32 (radix).
  • Deje que stripPrefix sea verdadero.
  • Si R ≠ 0, entonces a. Si R <2 o R> 36, entonces devuelve NaN. si. Si R ≠ 16, deje que stripPrefix sea falso.
  • De lo contrario, R = 0 a. Deje R = 10.
  • Si stripPrefix es verdadero, entonces a. Si la longitud de S es al menos 2 y los dos primeros caracteres de S son "0x" o "0X", elimine los dos primeros caracteres de S y deje R = 16.
  • Si S contiene algún carácter que no sea un dígito radix-R, entonces Z sea la subcadena de S que consta de todos los caracteres antes del primer carácter; de lo contrario, que Z sea S.
  • Si Z está vacío, devuelve NaN.
  • Sea matemático el valor entero matemático que está representado por Z en notación radix-R, usando las letras AZ y az para dígitos con valores del 10 al 35. (Sin embargo, si R es 10 y Z contiene más de 20 dígitos significativos, cada significativo el dígito después del 20 puede ser reemplazado por un dígito 0, a opción de la implementación; y si R no es 2, 4, 8, 10, 16 o 32, entonces matemática puede ser una aproximación dependiente de la implementación al entero matemático valor representado por Z en notación radix-R.)
  • Sea número el valor de Número para mathInt.
  • Signo de retorno × número.

NOTA parseInt puede interpretar solo una parte inicial de la cadena como un valor entero; ignora los caracteres que no se pueden interpretar como parte de la notación de un número entero, y no se da ninguna indicación de que dichos caracteres hayan sido ignorados.

Hay dos partes importantes aquí. Los en negrita los dos. Entonces, antes que nada, tenemos que descubrir cuál es la toStringrepresentación de null. Necesitamos mirar Table 13 — ToString Conversionsen la sección 9.8.0 para esa información:

ingrese la descripción de la imagen aquí

Genial, así que ahora sabemos que hacer toString(null)internamente produce una 'null'cadena. Genial, pero ¿cómo maneja exactamente los dígitos (caracteres) que no son válidos dentro de la raíz proporcionada?

Miramos arriba 15.1.2.2y vemos la siguiente observación:

Si S contiene algún carácter que no sea un dígito radix-R, entonces Z sea la subcadena de S que consta de todos los caracteres antes del primer carácter; de lo contrario, que Z sea S.

Eso significa que manejamos todos los dígitos ANTES de la raíz especificada e ignoramos todo lo demás.

Básicamente, hacer parseInt(null, 23)es lo mismo que parseInt('null', 23). Esto uhace que los dos lse ignoren (a pesar de que SON parte de la raíz 23). Por lo tanto, solo podemos analizar n, haciendo que toda la declaración sea sinónimo de parseInt('n', 23). :)

De cualquier manera, ¡gran pregunta!

David Titarenco
fuente
33
parseInt( null, 24 ) === 23

Es equivalente a

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

que es equivalente a

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

Los dígitos para la base 24 son 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, ..., n.

La especificación del idioma dice

  1. Si S contiene algún carácter que no sea un dígito radix-R, entonces Z sea la subcadena de S que consta de todos los caracteres antes del primer carácter; de lo contrario, que Z sea S.

que es la parte que garantiza que los literales enteros de estilo C como 15Lanalizar correctamente, por lo que lo anterior es equivalente a

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

"n" es la letra 23 de la lista de dígitos anterior.

QED

Mike Samuel
fuente
16

Supongo que nullse convierte en una cadena "null". Entonces, en nrealidad está 23en 'base24' (lo mismo en 'base25' +), uno es válido en 'base24', por nulllo que se ignorará el resto de la cadena . Es por eso que sale 23hasta uque sea válido en 'base31'.

Floern
fuente
7

parseInt usa representación alfanumérica, luego en base-24 "n" es válido, pero "u" es un carácter no válido, luego parseInt solo analiza el valor "n" ...

parseInt("n",24) -> 23

como ejemplo, prueba con esto:

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

El resultado será "3".

fdaines
fuente