Há pouco tempo, o
JavaScript ostentava um novo
tipo de dados
BigInt primitivo para trabalhar com números de precisão arbitrários. O mínimo necessário de informações já foi
informado /
traduzido sobre motivação e casos de uso. E eu gostaria de prestar um pouco mais de atenção à "explicitação" local exagerada na conversão de tipos e no inesperado
TypeError . Iremos repreender ou entender e perdoar (de novo)?
Implícito se torna explícito?
Em um idioma em que a conversão implícita de tipo está acostumada há muito tempo, tornou-se um meme de quase todas as conferências e poucas pessoas são surpreendidas por complicações como:
1 + {};
De repente, obtemos um TypeError, tentando adicionar dois NÚMEROS aparentemente:
1 + 1n;
E se a experiência anterior de implicitness não levou a um colapso no aprendizado da linguagem, há uma segunda chance de quebrar e jogar fora o livro da ECMA e entrar em algum Java.
Além disso, a linguagem continua a "trollar" os desenvolvedores js:
1n + '1';
Ah, sim, não se esqueça do operador unário
+ :
+1n;
Em resumo, não podemos misturar
BigInt e
Number em operações. Como resultado, não é recomendável usar "números inteiros grandes" se 2 ^ 53-1 (
MAX_SAFE_INTEGER ) for suficiente para nossos propósitos.
Decisão-chave
Sim, essa foi a principal decisão dessa inovação. Se você esquecer que isso é JavaScript, tudo será lógico: essas conversões implícitas contribuem para a perda de informações.
Quando adicionamos dois valores de tipos numéricos diferentes (números inteiros grandes e números de ponto flutuante), o valor matemático do resultado pode estar fora do intervalo de valores possíveis. Por exemplo, o valor da expressão
(2n ** 53n + 1n) + 0,5 não pode ser representado com precisão por nenhum desses tipos. Isso não é mais um número inteiro, mas um número real, mas sua precisão não é mais garantida pelo
formato float64 :
2n ** 53n + 1n;
Na maioria das linguagens dinâmicas, onde são apresentados os tipos para números inteiros e flutuantes, o primeiro é escrito como
1 e o segundo como
1.0 . Assim, durante operações aritméticas na presença de um separador decimal no operando, podemos concluir que a precisão da flutuação nos cálculos é aceitável. Mas o JavaScript não é um deles, e
1 é um flutuador! E isso significa que a computação
2n ** 53n + 1 retornará a flutuação 2 ^ 53. O que, por sua vez, quebra a funcionalidade principal do
BigInt :
2 ** 53 === 2 ** 53 + 1;
Bem, também não há motivo para falar sobre a implementação da
"torre numérica" , porque você não conseguirá considerar o número existente como um tipo de dados numérico geral (pelo mesmo motivo).
E para evitar esse problema, a
conversão implícita entre
Number e
BigInt nas operações foi proibida. Como resultado, o “número inteiro grande” não pode ser convertido com segurança em nenhuma função JavaScript ou API da Web, onde o número usual é esperado:
Math.max(1n, 10n);
Você deve selecionar explicitamente um dos dois tipos usando
Number () ou
BigInt () .
Além disso, para operações com tipos mistos, há
uma explicação sobre uma implementação complexa ou perda de desempenho, o que é bastante comum para comprometer inovações de linguagem.
Obviamente, isso se aplica a conversões numéricas implícitas com outras primitivas:
1 + true;
Mas as seguintes concatenações (já) funcionarão, pois o resultado esperado é uma sequência:
1n + [0];
Outra exceção está na forma de operadores de comparação (como
< ,
> e
== ) entre
Number e
BigInt . Também não há perda de precisão, pois o resultado é um booleano.
Bem, se você se lembra do novo tipo de dados
Symbol anterior, TypeError não parece mais uma adição tão radical?
Symbol() + 1;
E sim, mas não. De fato, conceitualmente, o símbolo não é um número, mas um todo - muito:
- É altamente improvável que o símbolo caia nessa situação. No entanto, isso é muito suspeito e TypeError é bastante apropriado aqui.
- É muito provável e usual que o “grande todo” nas operações se torne um dos operandos quando não há realmente nada de errado.
O operador unary
+ lança uma exceção devido a um problema de compatibilidade com
asm.js. , em que
Number é esperado. O unary plus não pode funcionar com
BigInt da mesma maneira que
Number , pois nesse caso o código asm.js anterior se tornará ambíguo.
Oferta alternativa
Apesar da relativa simplicidade e "limpeza" da implementação da
BigInt ,
Axel Rauschmeyer enfatiza a falta de inovação. Ou seja, sua única compatibilidade retroativa parcial com o
número existente e o seguinte:
Use números para ints de até 53 bits. Use Inteiros se precisar de mais bits
Como alternativa, ele
propôs o seguinte .
Deixe
Number se tornar o supertipo para o novo
Int e
Double :
- typeof 123.0 === 'número' e Number.isDouble (123.0) === true
- typeof 123 === 'número' e Number.isInt (123) === true
Com novas funções para as
conversões Number.asInt () e
Number.asDouble () . E, é claro, com sobrecarga do operador e os modelos necessários:
- Int × Duplo = Duplo (elenco)
- Duplo × Int = Duplo (com elenco)
- Duplo × Duplo = Duplo
- Int × Int = Int (todos os operadores, exceto a divisão)
Curiosamente, na versão simplificada, essa frase gerencia (a princípio) sem adicionar novos tipos ao idioma. Em vez disso, a
definição de The Number Type se expande: além de todos os números possíveis de precisão dupla de 64 bits (IEEE 754-2008), agora o número inclui todos os números inteiros.
Como resultado, o "número impreciso"
123.0 e o "número exato"
123 são números separados do tipo
Número único.
Parece muito familiar e razoável. No entanto, esta é uma atualização séria do número existente, com maior probabilidade de "quebrar a web" e suas ferramentas:
- Há uma diferença entre 1 e 1,0 , que não existia antes. O código existente os utiliza de forma intercambiável, que após a atualização pode causar confusão (diferentemente dos idiomas em que essa diferença estava presente inicialmente).
- Existe um efeito quando 1 === 1.0 (supõe-se que seja uma atualização) e, ao mesmo tempo, Number.isDouble (1)! == Number.isDouble (1.0) : novamente, é assim.
- A “peculiaridade” da igualdade 2 ^ 53 e 2 ^ 53 + 1 desaparece, o que quebrará o código que depende dela.
- O mesmo problema de compatibilidade com o asm.js. e mais.
Portanto, no final, temos uma solução de compromisso na forma de um novo tipo de dados separado. Vale apenas enfatizar que outra opção também foi considerada e
discutida .
Quando você se senta em duas cadeiras
Na verdade, o
comentário do comitê começa com as palavras:
Encontre um equilíbrio entre manter a intuição do usuário e preservar a precisão
Por um lado, finalmente quis acrescentar algo "exato" ao idioma. E, por outro lado, manter seu comportamento já familiar para muitos desenvolvedores.
Só que esse "exato" não pode ser adicionado, porque você não pode quebrá-lo: matemática, ergonomia da linguagem, asm.js., a
possibilidade de maior expansão do sistema de tipos , produtividade e, finalmente, a própria web! E você não pode quebrar tudo ao mesmo tempo, o que leva ao mesmo.
E você não pode quebrar a intuição dos usuários de idiomas, o que, é claro, também foi
muito debatido . Verdade, funcionou?