42

Tengo una duda sobre la existencia del famoso NaN, quisiera saber, ¿Para que sirve? ¿Cual es su "sentido existencial"?

Hasta ahora lo único que he podido entender en base a cosas que he visto en libros y en videos es que en resumen seria una especie de indicador de Error en operaciones matemáticas que nos suele avisar que hay una "invalidez"(como por ejemplo multiplicar un string por un integer); además su tipo de dato es numérico y su valor es indefinido y diferente a cualquier otro valor

¿Estoy en lo correcto? ¿sirve para algo mas?

agrego un par de citas:

NaN significa "No es un número". Cuando una función u operación matemática en JavaScript no puede devolver un número específico, en su lugar devuelve el valor NaN. Es una propiedad del objeto global, y una referencia a Number.NaN

JavaScriptNotes for Professionals - GoalKicker.com - Pg. 13.

Es una propiedad estatica especial numerica, NaN, que es equivalente a la NaN global, y es equivalente a Not-a-Number. Cada vez que intentes usar un valor en una operacion numerica que no se puede analizar como un numero, obtendrás un error de NaN

Javascript Cookbook, Shelley Powers - Pg. 56

NaN es una abreviatura de Not-a-Number y representa un número ilegal. Utiliza la función isNaN() para determinar si un número es legal o válido de acuerdo con la especificación ECMA-262.

JavaScript Step by Step 2ed. Steve Suehring - Pg. 63

El contexto de la pregunta seria solo en cuanto a su aplicacion en JavaScript, ya que de hablar en cuanto a programacion en general, aritmetica computacion u otros temas haria la pregunta mucho mas amplia y se volveria un poco ambigua; por otro lado, entiendo que puede ser logica la existencia de un "Error", "mensaje" o "alerta" para una situacion como la que se puede presentar al momento de operar numeros irracionales o sacar operaciones matematicas del "contexto" matematico, pero, ¿que puede diferir a NaN de los "tipicos" mensajes de Error?

JackNavaRow
  • 6,836
  • 5
  • 22
  • 49
Tux9000
  • 669
  • 6
  • 12
  • 1
    Interesante pregunta pero no está del todo claro si se trata de una pregunta de aritmética computacional o del caso específico de JavaScript. Tu pregunta sería mejor si incluyeras algunas referencias de las fuentes que mencionas que has consultado. – Rubén Jul 01 '19 at 03:20
  • 3
    @Rubén gracias, ya complemente la pregunta, es especifica en cuanto a JS, supongo que en cuanto a la aritmetica computaciones podria ser una operacion ilogica, podria ser visto como una especie de "falacia", ¿verdad? – Tux9000 Jul 01 '19 at 04:11
  • 2
    Puedes leer esta pregunta [¿Por qué NaN != NaN?](https://es.stackoverflow.com/questions/4603/por-qu%c3%a9-nan-nan) – JackNavaRow Jul 01 '19 at 13:31
  • 3
    Relacionado :[¿Por qué mis programas no pueden hacer cálculos aritméticos correctamente?](https://es.stackoverflow.com/questions/197/por-qu%c3%a9-mis-programas-no-pueden-hacer-c%c3%a1lculos-aritm%c3%a9ticos-correctamente) – JackNavaRow Jul 01 '19 at 16:15
  • 1
    Encontré esta operación que da como resultado 1. `Math.pow(NaN,0)` –  Jul 10 '19 at 00:09
  • Que curioso eso, aca explican una parte de eso https://stackoverflow.com/questions/19976978/is-there-any-x-value-where-math-powx-0-is-not-1 https://stackoverflow.com/questions/19955968/why-is-math-pow0-0-1 y por lo visto no es algo unico de Javascript, sino, que tambien aplica en otros lenguajes como Java(Java Precisely 3ed. Peter Sestoft - Página 77) – Tux9000 Jul 14 '19 at 02:49

2 Answers2

58

¿Quién lo ha inventado?

NaN no es un invento de JavaScript. Es parte de la especificación IEEE-754 que define cómo representar en binario los números reales. En esta especificación se basan todas las implementaciones actuales de los números reales usados por los diferentes lenguajes de programación, como los tipos float (correspondiente al IEEE-754 de precisión simple que usa 32 bits para representar los reales) y double (correspondiente al IEEE-754 de precisión ampliada, que usa 64 bits para representar los reales) del C. No sólo el C, sino prácticamente cualquier lenguaje actual usa este estándar, debido a que el propio hardware (el procesador matemático de la CPU) lo usa. En el caso de JavaScript, además, el lenguaje carece del tipo entero (int) por lo que cualquier número JavaScript es en realidad un double. Volveremos a JavaScript después. De momento centrémonos en IEEE-754.

Dejando de lado los problemas de rango y precisión, que darían para otra pregunta y que tratan de cómo es posible meter todos los reales (que son infinitos, de hecho un infinito de mayor orden que el infinito de los naturales) en sólo 32 ó 64 bits (spoiler, no se puede :-), y por eso pasan cosas raras) está el problema adicional de que ciertas operaciones matemáticas están definidas para un subconjunto de los reales, pero no para todos. En este problema me centraré aquí.

¿Por qué era necesario?

La división, por ejemplo. Es posible dividir dos reales cualesquiera y el resultado será otro real, salvo para el caso de que el divisor sea cero. En ese caso, se adopta el convenio matemático (para el cual hay buenas razones en las que no voy a entrar) de que el resultado sea "infinito" (o infinito negativo si el numerador era negaivo).

"infinito" no es un número real. Es un concepto. No hay ningún real igual a infinito. No obstante, si tenemos una operación "división" cuyo tipo retornado sea float o double, entonces debemos tener previsto una combinación de (32 ó 64) bits que represente "inifinto" (y otra para "infinito negativo"). El estándar IEEE-754 tiene esto previsto.

No basta con infinito. Hay otras operaciones matemáticas que no tienen resultado definido, y para las que no tiene sentido decir que "sale infinito". Por ejemplo, las raíces cuadradas de cualquier número negativo no tienen resultado dentro de los reales. Lo mismo pasa con el logaritmo de cero, o de cualquier negativo. O con el arcoseno de cualquier número que no esté comprendido entre 0 y 1, etc. Hay muchos ejemplos de funciones que están definidas sólo para un subconjunto de los reales. Tratar de evaluarlas sobre un elemento que no esté en ese subconjunto debería tratarse como un error.

En lenguajes que soporten excepciones, como JavaScript, Python, Java, etc. podría hacerse uso de este mecanismo para señalar el problema. De hecho, Python por ejemplo así lo hace, y al intentar calcular math.log(-1) se obtiene la excepción ValueError. Sin embargo en otros lenguajes más primitivos como C o ensamblador, en los que no hay excepciones, es necesario tener prevista una cierta combinación de bits, similar al caso del infinito antes visto, que represente que la operación no es válida. O bien que "el resultado no es un real". Not a Number. NaN.

IEEE-754 tiene esto previsto también, y tiene un código (de hecho un gran número de ellos) para codificar el concepto "No es un número". El procesador matemático de la CPU es el mecanismo que utiliza, y lo que retorna como resultado en las operaciones ilegales. Las funciones C también pueden retornar ese valor (pues es un elemento válido del tipo float o double). Los lenguajes de más alto nivel pueden optar por detectar este resultado y elevar una excepción (como hace Python), o "dejarlo pasar" y retornar NaN (como hace JavaScript).

¿Qué representación binaria tiene?

El código binario que representa NaN en IEEE-754 de precisión simple (32 bits) es el x11111111xxxxxxxxxxxxxxxxxxxxxxx, siendo cada x un bit que puede valer 1 ó 0, salvo que las últimas 23 x no pueden ser todas 0 (pues en ese caso estaría representando infinito, siendo el primer bit el signo). Como vemos el estándar no define un código único para NaN, sino que de hecho tenemos 2^(24)-2 posibles representaciones. No obstante todas se consideran equivalentes, es decir, conceptualmente hay "un solo NaN" que puede representarse de muchas formas.

En el caso del IEEE-754 de precisión doble, aún hay más posibilidades, ya que en este formato se tiene que NaN es cualquier patrón de bits del tipo x11111111111xxxx...xxx en el que tenemos un primer bit que puede ser 1 ó 0, otros once bits que deben ser 1, y otros 52 bits que pueden tomar cualquier valor con tal de que no sean todos ellos 0. Con este patrón se pueden generar 2^(53)-2 códigos diferentes.

¿Por qué tantos códigos diferentes para representar NaN? La verdad es que lo desconozco. Uno solo valdría. Supongo que quienes diseñaron este estándar no encontraron otra forma de aprovechar los restantes 2^(24)-1 [o 2^(53)-1 en precisión doble] códigos.

Edición. Como menciona @JoseManuelRamos en un comentario, la razón de ese número de códigos "reservados" nace de que todos los códigos IEEE-754 que tienen todo 1 en el campo exponente, se reservan para valores especiales (dos de ellos serían infinito y menos infinito, y los restantes serían NaN.

Pero la pregunta siguiente es entonces ¿por qué ese "desperdicio"? ¿No podrían aprovecharse de algún modo los bits de la mantisa en todos esos casos para codificar algo diferente? La respuesta, que originalmente no conocía pero después he averiguado, es que si se pueden usar, para meter en ellos lo que se llama el NaN payload. En teoría, si una operación da como resultado NaN, el código binario que la representa tendrá todo 1 en la parte exponente y otro código (distinto de todo ceros) en la parte mantisa, y lo que signifique ese código depende de la aplicación, que puede utilizarlo como desee. Por ejemplo, podría contener información sobre qué operación se estaba haciendo cuando se produjo el error. ¡Hay sitio para muchos códigos!

El problema es que no está estandarizado su uso, y por tanto en la práctica no puede usarse de forma fiable para nada, aunque es una opción disponible para implementar ideas creativas.

Parece ser que algunas implementaciones del intérpretes de JavaScript lo usaron para ahorrarse unos bits en la implementación del modelo de datos, mediante un truco (casi un hack) que llamaban NaN boxing. La idea era usar esos 52 bits de mantisa disponibles en un double para guardar un puntero de 32 bits y un tag de 20 bits. El tag indicaría de a qué tipo apunta el puntero (número, cadena, lista, objeto, etc.) Además, si el exponente no es "todo 1", sería un double normal, por lo que en un mismo tipo "subyacente" (double, 64 bits) se pueden implementar muchos tipos diferentes.

¿Por qué NaN != NaN?

Básicamente para evitar errores de lógica. Si tienes que f(x) == f(y) podrías pensar que x == y. Esto es correcto para muchas funciones. Por ejemplo, la raiz cuadrada. Si Math.sqrt(a) == Math.sqrt(b) uno podría deducir que entonces a == b. En efecto, si por ejemplo Math.sqrt(a) == 5 y lo mismo para Math.sqrt(b), podremos deducir que tanto a como b valen 25.

Pero ¿y si a=-1 mientras b=-4? En ese caso tanto Math.sqrt(a) como Math.sqrt(b) darán como resultado NaN. Si aceptásemos que NaN == NaN, podríamos llegar a la conclusión errónea de que a==b. Por tanto por definicion NaN siempre es diferente de NaN (incluso si "por debajo" se representan con el mismo código binario).

De hecho, una vez que una operación ha producido NaN, cualquier otra operación que use NaN como argumento debería dar como resultado NaN a su vez. Si permitiéramos que NaN == NaN eso implicaría que NaN/NaN debería ser 1, lo que imposibilitaría la "propagación" del resultado erróneo entre operaciones.

Rarezas

NaN es un valor del tipo float (o double) como ya se ha dicho. Ya que JavaScript es el único tipo numérico que tiene, al que denomina genéricamente "number", resulta que NaN es un "number" válido. Lo que no deja de tener su gracia.

>>> typeof(NaN)
"number"

JavaScript usa NaN en contextos más allá de los previstos por el estándar. Por ejemplo, si se intenta dividir un número entre una cadena, se tendrá un error, pero de diferente naturaleza que el de Math.log(-1). En el caso de 1/"cadena" es un error de tipos. Ni siquiera el tipo de retorno estaría definido en este caso. JavaScript decide que el resultado sea de tipo "number", y ya que no le puede dar ningún valor, le da el valor NaN.

>>> typeof(1/"cadena")
"number"
>>> 1/"cadena"
NaN

Finalmente, podría pensarse que las mismas razones esgrimidas para defender que NaN != NaN se aplicarían a "infinito", puesto que si a/0 == b/0 eso no implica a == b, pero en cambio el estándar IEEE-754 no lo considera así. Podemos comprobarlo con JavaScript:

>>> Infinity == Infinity
true

¿NaN o excepción?

Podría argüirse que un intento de computar Math.log(-1) es un error que debería producir una excepción. Algunos lenguajes hacen esto al detectar que un resultado es NaN. Sin embargo los ingenieros que diseñaro el estándar IEEE-754 prefirieron retornar un "valor especial" antes que generar una excepción hardware, pues eso simplificaba en aquél momento la implementación.

Insisto además en el hecho de que, tal como está definido el comportamiento de NaN, una vez que una operación ha producido NaN, cualquier otra que use el resultado anterior seguirá dando como resultado NaN. En concreto NaN - NaN = NaN (otro argumento por el que NaN!=NaN, ya que la comparación suele implementarse a nivel hardware con una resta). Este comportamiento "viral" de NaN recuerda mucho a cómo una excepción se propaga hacia arriba desde la función que la generó a las funciones que la llamaron.

Además pueden darse casos en los que retornar NaN sea más útil que lanzar una excepción. Un ejemplo típico es la búsqueda de ceros en una función, por un método similar al de Newton. El problema es, dada una función f(x) encontrar un valor de x para el cual f(x) sea cero. El método de Newton y otros similares se basan en probar un par de valores de x y si en ellos f(x) es distinto de cero, usar el resultado para "afinar" mejor el próximo intento. Sin entrar en más detalles, podemos ver que si f(x) no está definida para todo posible x puede darse el caso de que intentemos un x para el que no está definida. Generar una excepción abortaría el método, mientras que retornar NaN le permitiría proseguir e intentar otra x. Pero en realidad esta justficación me parece endeble. Lo mismo podría lograrse (creo yo) capturando la excepción e intentando otra x.

La verdadera razón de la existencia de NaN hay que buscarla en sus raíces históricas. En la época en que se diseñó, esta solución era mucho más fácil de implementar sin apenas tener que modificar los compiladores y lenguajes existentes. Por ejemplo, C, el lenguaje más importante entonces (y quizás aún ahora) no tiene excepciones, y por el contrario basa su gestión de errores en que las funciones retornen "valores especiales" para indicar que hubo un error (-1 si el tipo retornado es int, NULL si el tipo retornado es puntero, y NaN si el tipo retornado es float o double).

JavaScript parece heredero de esta corriente, pues NaN no es el único caso en el que este lenguaje ha optado por devolver un valor "especial" en lugar de generar una excepción. Por ejemplo, el acceso a un array fuera de sus límites (que en otros lenguajes generaría una excepción), causa en JS que el valor obtenido sea undefined:

> a = [0,1,2]
> a[8]
undefined
abulafia
  • 53,696
  • 3
  • 45
  • 80
  • 2
    Gracias, junto con las demas respuestas me ayudaron a comprender a fondo la razon de su existencia, para concluir, en cuanto a javascript respecta, ¿No tendria diferencia con mensaje de error? ¿porque no simplemente se le dio la "forma" de mensaje de error? – Tux9000 Jul 02 '19 at 05:04
  • 2
    @Tux9000 He añadido una sección al final comparando `NaN` con otras formas de señalar errores. – abulafia Jul 02 '19 at 11:35
  • 2
    `¿Por qué tantos códigos diferentes para representar NaN? La verdad es que lo desconozco.` --- Porque el estándar especifica que el exponente con todo unos, indiferentemente del valor de mantisa o signo, sea considerado "números especiales". --- Sólo cuando la mantisa es todo ceros, se le llama al "NaN" de forma distinta, pero sigue siendo un "NaN" --- La raíz cuadrada de un número negativo, división entre cero, logaritmo de 0 y otras operaciones que desbarajustan el sistema de punto flotante: http://www.gnu.org/savannah-checkouts/gnu/libc/manual/html_node/FP-Exceptions.html#FP-Exceptions – José Manuel Ramos Jul 03 '19 at 16:46
  • 2
    @JoséManuelRamos Gracias por el apunte, pero la verdadera pregunta era ¿no se podrían haber usado para algo todos esos códigos reservados? He investigado un poco, y acabo de ampliar la pregunta – abulafia Jul 03 '19 at 17:37
25

¿Para que sirve el NaN?

  • Comprobar y asegurarte de que estas tratando con números. isNaN
  • Identificar rápidamente donde está el error. (Si has hecho parseInt, parseFloat... y devuelve NaN ya sabes que el valor es un nulo o un String vació)

Muchos métodos de JavaScript (como son el Number constructor, parseFloat y parseInt) retornan NaN si el valor especificado en el parámetro no puede ser parseado como un número.

Sabiendo esto:

var nullVar = null;
var stringVacio = "";
console.log(isNaN(nullVar)); //Retorna false 
console.log(isNaN(stringVacio)); //Retorna false

¿Por que retornan false?

Retornan false por que el valor de un string vacio "" o un null es convertido a 0

Por lo que podremos operar con ellos:

var stringVacio = "";
console.log((stringVacio +2));

Entonces... ¿Donde esta el NaN?

Pues como pone en la cita al principio de la pregunta, si hacemos el parse a estos valores antes de operar con ellos obtendremos NaN

var stringVacio = "";
console.log((parseInt(stringVacio) +2));

La forma correcta de comprobar con isNaN sería:

var nullVar = null;
var stringVacio = "";
console.log(isNaN(parseInt(nullVar))); //Retorna true al hacerle el parseInt
console.log(isNaN(parseInt(stringVacio))); //Retorna true al hacerle el parseInt
x3k
  • 3,732
  • 10
  • 38
  • 4
    Gracias, ¿eso quisiera decir que *NaN* no deja de ser mas que un "vulgar" mensaje de error (por asi decirlo)? asi como por ejemplo el mensaje de error de ***ReferenceError...*** que suele aparecer cuando "llamamos" una variable indefinida – Tux9000 Jul 02 '19 at 03:09