No hace mucho tiempo,
JavaScript contaba con un nuevo
tipo de datos primitivo
BigInt para trabajar con números de precisión arbitrarios. Ya se ha
contado /
traducido el mínimo necesario de información sobre motivación y casos de uso. Y me gustaría prestar un poco más de atención a la "explicitación" local exagerada en la conversión de tipos y al inesperado
TypeError . ¿Regañaremos o entenderemos y perdonaremos (nuevamente)?
¿Lo implícito se vuelve explícito?
En un lenguaje donde la conversión de tipo implícita se ha utilizado durante mucho tiempo, se ha convertido en un meme de casi cualquier conferencia y pocas personas se sorprenden de complejidades como:
1 + {};
De repente recibimos un TypeError, tratando de agregar dos NÚMEROS aparentemente:
1 + 1n;
Y si la experiencia previa de cosas implícitas no condujo a un colapso en el aprendizaje del idioma, entonces hay una segunda oportunidad para desglosar y tirar el libro de texto de ECMA y entrar en algunos Java.
Además, el lenguaje continúa "troll" a los desarrolladores de js:
1n + '1';
Ah sí, no te olvides del operador unario
+ :
+1n;
En resumen, no podemos mezclar
BigInt y
Number en las operaciones. Como resultado, no se recomienda utilizar "enteros grandes" si 2 ^ 53-1 (
MAX_SAFE_INTEGER ) es suficiente para nuestros propósitos.
Decisión clave
Sí, esta fue la decisión principal de esta innovación. Si olvida que esto es JavaScript, entonces todo es muy lógico: estas conversiones implícitas contribuyen a la pérdida de información.
Cuando agregamos dos valores de diferentes tipos numéricos (enteros grandes y números de coma flotante), el valor matemático del resultado puede estar fuera de su rango de valores posibles. Por ejemplo, el valor de la expresión
(2n ** 53n + 1n) + 0.5 no puede representarse con precisión por ninguno de estos tipos. Esto ya no es un número entero, sino un número real, pero su precisión ya no está garantizada por el
formato float64 :
2n ** 53n + 1n;
En la mayoría de los lenguajes dinámicos, donde se representan los tipos para números enteros y flotantes, los primeros se escriben como
1 y los segundos se escriben como
1.0 . Por lo tanto, durante las operaciones aritméticas en presencia de un separador decimal en el operando, podemos concluir que la precisión de flotación en los cálculos es aceptable. ¡Pero JavaScript no es uno de ellos, y
1 es flotante! Y esto significa que calcular
2n ** 53n + 1 devolverá float 2 ^ 53. Lo que, a su vez, rompe la funcionalidad clave de
BigInt :
2 ** 53 === 2 ** 53 + 1;
Bueno, tampoco hay razón para hablar sobre la implementación de la
"torre numérica" , porque no logrará tomar el número existente como un tipo de datos numéricos generales (por la misma razón).
Y para evitar este problema, se prohibió el reparto implícito entre
Number y
BigInt en las operaciones. Como resultado, el "gran número entero" no se puede convertir de forma segura en ninguna función de JavaScript o API web, donde se espera el número habitual:
Math.max(1n, 10n);
Debe seleccionar explícitamente uno de los dos tipos utilizando
Number () o
BigInt () .
Además, para operaciones con tipos mixtos, hay
una explicación sobre una implementación compleja o pérdida de rendimiento, que es bastante común para las innovaciones de lenguaje comprometido.
Por supuesto, esto se aplica a las conversiones numéricas implícitas con otras primitivas:
1 + true;
Pero las siguientes (ya) concatenaciones funcionarán, ya que el resultado esperado es una cadena:
1n + [0];
Otra excepción es en forma de operadores de comparación (como
< ,
> y
== ) entre
Number y
BigInt . Tampoco hay pérdida de precisión, ya que el resultado es un booleano.
Bueno, si recuerda el nuevo tipo de datos de
Symbol anterior, ¿TypeError ya no parece una adición tan radical?
Symbol() + 1;
Y sí, pero no. De hecho, el símbolo conceptual no es un número en absoluto, sino un todo, mucho:
- Es muy poco probable que el símbolo caiga en tal situación. Sin embargo, esto es muy sospechoso y TypeError es bastante apropiado aquí.
- Es muy probable y habitual que el "gran todo" en las operaciones se convierta en uno de los operandos cuando realmente no hay nada malo.
El operador unario
+ genera una excepción debido a un problema de compatibilidad con
asm.js , donde se espera
Number . El plus unario no puede funcionar con
BigInt de la misma manera que
Number , ya que en este caso el código asm.js anterior será ambiguo.
Oferta alternativa
A pesar de la relativa simplicidad y "limpieza" de la implementación de
BigInt ,
Axel Rauschmeyer enfatiza la falta de innovación. A saber, su única compatibilidad con versiones anteriores con el
Número existente y el siguiente:
Use números para entradas de hasta 53 bits. Use números enteros si necesita más bits
Como alternativa,
propuso lo siguiente .
Deje que
Number se convierta en el supertipo para el nuevo
Int y
Double :
- typeof 123.0 === 'número' y Number.isDouble (123.0) === verdadero
- typeof 123 === 'número' y Number.isInt (123) === verdadero
Con nuevas funciones para las
conversiones Number.asInt () y
Number.asDouble () . Y, por supuesto, con la sobrecarga del operador y los moldes necesarios:
- Int × Double = Double (cast)
- Doble × Int = Doble (con yeso)
- Doble × Doble = Doble
- Int × Int = Int (todos los operadores excepto la división)
Curiosamente, en la versión simplificada, esta oración se administra (al principio) sin agregar nuevos tipos al idioma. En cambio, la
definición de The Number Type se expande: además de todos los números posibles de doble precisión de 64 bits (IEEE 754-2008), el número ahora incluye todos los enteros.
Como resultado, el "número inexacto"
123.0 y el "número exacto"
123 son números separados del tipo de
Número único.
Se ve muy familiar y razonable. Sin embargo, esta es una actualización seria del número existente, que es más probable que "rompa la web" y sus herramientas:
- Hay una diferencia entre 1 y 1.0 , que no estaba allí antes. El código existente los usa indistintamente, lo que después de la actualización puede generar confusión (a diferencia de los idiomas donde esta diferencia estaba presente inicialmente).
- Hay un efecto cuando 1 === 1.0 (se supone que es una actualización), y al mismo tiempo, Number.isDouble (1)! == Number.isDouble (1.0) : de nuevo, es así.
- La "peculiaridad" de la igualdad 2 ^ 53 y 2 ^ 53 + 1 desaparece, lo que romperá el código que se basa en ella.
- El mismo problema de compatibilidad con asm.js y más.
Por lo tanto, al final, tenemos una solución de compromiso en forma de un nuevo tipo de datos separado. Vale la pena enfatizar que también se consideró y
discutió otra opción.
Cuando te sientas en dos sillas
En realidad, el
comentario del comité comienza con las palabras:
Encuentre un equilibrio entre mantener la intuición del usuario y preservar la precisión
Por un lado, finalmente quería agregar algo "exacto" al idioma. Y, por otro lado, para mantener su comportamiento ya familiar para muchos desarrolladores.
Es solo que no podrá agregar esto "exacto", porque no puede romperlo: matemáticas, ergonomía del lenguaje, asm.js, la
posibilidad de una mayor expansión del sistema de tipos , productividad y, al final, ¡la propia web! Y no puedes romperlo todo al mismo tiempo, lo que lleva a lo mismo.
Y no se puede romper la intuición de los usuarios del lenguaje, que, por supuesto, también se
debatió acaloradamente . Es cierto, ¿funcionó?