nomar.dev

11011000001

26 de Abril de 2020

Números especiais no JavaScript e suas implicações

Como se retorna o curso de uma antiga vida, como se segue em frente quando, no íntimo, começa-se a entender que não há volta?

Frodo Bolseiro

Quanto é um divido por zero? Bom, não é possível determinar o resultado dessa divisão pois teríamos que encontrar um número que multiplicado por zero resultasse no valor 1. Entretanto qualquer número multiplicado por zero é igual a zero (0 * qualquerNumero === 0).

Indo um pouco mais a fundo e considerando essa característica do número zero em relação a multiplicação, podemos utilizar um método especial para verificar o que acontece quando usamos números próximo de zero na divisão por um.

numeroProximoDeZero1 / numeroProximoDeZero
11
0,52
0,25
0,110
0,0520
0,01100
0,0011000
0,000110000

Perceba que a medida que nos aproximamos de zero, o resultado da divisão é um número cada vez maior. Assim, o resultado de 1 / numeroProximoDeZero pode ser um número tão grande quanto quisermos desde que fosse escolhido um valor suficientemente próximo de zero. Para representar esse tipo de comportamento é utilizado o conceito de infinito (∞).

Vamos fazer mais uma verificação, dessa vez com número próximos de zero e que sejam menores que zero (números negativos).

negativoProximoDeZero1 / negativoProximoDeZero
-1-1
-0,5-2
-0,2-5
-0,1-10
-0,05-20
-0,01-100
-0,001-1000
-0,0001-10000

O comportamento é parecido quando utilizamos números positivos com a diferença que agora os valores ficam "cada vez maiores" com o valor negativo. Esse tipo de comportamento é representado pelo símbolo do infinito com sinal negativo: -∞.

Pode parecer um pouco estranho, mas temos dois infinitos: um positivo e um negativo. O que fizemos acima foi calcular o limite [1] da função 1/x. Apesar de interessante, não é necessário um conhecimento prévio de cálculo ou limite para entender os tópicos abordados nesse post.

Quando se está programando, o que ocorre quando dividimos um por zero? A resposta é depende de onde você estiver programando. Os cenários possíveis (não limitados a estes) são: gerar uma exceção, uma mensagem de erro, um infinito positivo ou negativo, uma valor indicando que não é um número válido ou fechar o programa.

No JavaScript, 1/0 resulta no número especial Infinity. Também temos o infinito negativo (-Infinity) que pode ser gerado por -1/0. A divisão por zero em ambos os casos resulta em um valor do tipo number e não é gerada nenhuma exceção. Dessa forma, não adianta colocar uma divisão dentro de um bloco try..catch para pegar uma possível divisão por zero. Essa informação é especialmente importante se você veio de uma outra linguagem de programação que gera uma exceção nesse caso.

// NÃO faça isso para pegar uma divisão por zero.
// O bloco catch não será executado pois nenhuma exceção é gerada
try {
  let result = 1/0;
} catch (error) {
  console.log(error, 'Divisão por zero');
}

Podemos checar se um número é Infinity de duas formas. A primeira é utilizando o operador de igualdade estrita === (strict equality). A segunda é utilizando a função isFinite do objeto Number. É preciso ter atenção ao utilizar a função isFinite pois ela retorna true se a entrada é um número finito. Quando a entrada é Infinity ou -Infinity, o resultado é false. Entretanto para outros números especiais e tipos (string, boolean, etc) o resultado também é false. Assim, esse função nos permite apenas afirmar de forma categórica que a entrada é um número finito quando o resultado for true. Se o resultado for false, a entrada pode ser Infinity, -Infinity, uma string, um boolean, um objetco, etc.

1/0 === Infinity // true
-1/0 === -Infinity // true
Infinity === -Infinity // false

Number.isFinite(1/0) // false
Number.isFinite(Infinity) // false
Number.isFinite(-Infinity) // false
Number.isFinite('hello') // false
Number.isFinite(1) // true

Podemos realizar algumas operações aritméticas com Infinity. Por exemplo, 3 * Infinity === Infinity, Infinity + Infinity === Infinity e 1/Infinity === 0. Entretanto algumas operações resultam em um outro número especial chamado de NaN (not a number - não é um número): Infinity - Infinity === NaN e Infinity / Infinity === NaN.

Não se deixe enganar, apesar do nome, NaN é do tipo number. O JavaScript utiliza o NaN para indicar que um resultado não possui um significado computacional [2]. Na matemática, a expressão Infinity / Infinity é indeterminado pois o infinito não é visto como um número, mas como uma forma de expressar um "comportamento".

Um NaN é gerado quando tentamos converter algo que não pode ser convertido para o tipo number, tentamos realizar uma operação aritmética com um tipo que não pode ser convertido para number, realizamos uma operação aritmética com um NaN ou por meio de algumas funções do objeto Math.

Number( 'hello' ) // NaN
Number( {} ) // NaN
Number( undefined ) // NaN

'a' - 1 // NaN
undefined * 1 // NaN
'a' / 1 // NaN

NaN + 1 // NaN
NaN - 1 // NaN
NaN * 1 // NaN
NaN / 1 // NaN

Math.sqrt( -1 ) // NaN não existe raiz quadrada de número negativo
Math.log( -1 ) // NaN não existe log de número negativo

No segundo caso, operação aritmética com um tipo que não pode ser convertido para number, não foi feita a operação de adição. Nesse tipo de operação, o resultado é uma string concatenada: 'a' + 1 === 'a1'. Ou seja, ao invés de tentar converter a string 'a' para number, é convertido o número 1 para string e depois é feita a concatenação.

Sabendo o significado de um NaN e em quais circustâncias ele é gerado, temos que descobrir como verificar se um valor é um NaN. Talvez venha a cabeça utilizar o operador de igualdade estrita (===). Já que ele foi utilizado no Infinity. Entretanto NaN é o único valor que não é igual a ele mesmo. Isso significa que NaN === NaN resulta em false. Apesar de não fazer sentido (e confundir), vamos apenas aceitar isso como um fato. Qualquer valor comparado com NaN resulta em false, mesmo que o valor comparado seja um "outro" NaN.

Felizmente existem outras três formas de checarmos se um valor é um NaN:

Number.isNaN( NaN ) // true
Number.isNaN( 'hello' ) // false

Object.is( NaN, NaN ) // true

NaN !== NaN // true

A função Number.isNaN() retorna true se a entrada for NaN. Caso contrário, retorna false. Essa função é uma reimplementação da função isNaN (sem o "Number"). Esta é suportada em todos os navegadores enquanto aquela não possui suporte no Internet Explorer. Além disso, isNaN tenta converter a entrada para o tipo Number, se for possível, o retorno é false. Caso contrário, retorna true.

// diferença entre isNaN e Number.isNaN
isNaN('hello') // true
Number.isNaN('hello') // false

A função Object.is() é utilizada para verificar se dois valores são o mesmo valor.

Por fim, temos o operador de comparação !==. Esse é o que pode causar mais confusão por ser nenhum pouco intuitivo. Para entender o uso desse operador temos que lembrar que NaN é o único valor no JavaScript que não é igual a ele mesmo. Em razão disso, qualquer valor diferente de NaN, resultará em false se for comparado com ele mesmo. Exemplo: 1 !== 1 retornará false enquanto NaN !== NaN retornará true.

O último número especial a ser abordado é o -0 (zero negativo). Essa representação é útil em operações com números de ponto-flutuante. Os valores 0 e -0 são diferentes, entretanto ao utilizar o operador de igualdade estrita (===) para comparar esses valores, o resultado é true: 0 === -0. Para verificar esses valores utiliza-se a função Object.is(): Object.is(0, -0) é igual a false (que é o valor esperado já que são número diferentes).

Vimos os três números especiais que existem no JavaScript. São apenas três, mas que podem confundir bastante. Na prática é provável que em algum momento desenvolvendo com JavaScript você esbarre (ou já esbarrou) apenas com o NaN.

Resumo

  • Números especiais no JavaScript
    • Infinity e -Infinity
    • NaN
    • -0 (zero negativo)
  • Infinity e -Infinity
    • 1 / 0 resulta em Infinity
    • -1 / 0 resulta em -Infinity
    • Verificação:
      • Comparação com o operador de igualdade estrita: 1/0 === Infinity // true
      • Função Number.IsFinite() - informa que um valor é um número finito.
  • NaN - indicar que um resultado não possui um significado computacional
    • Number( 'hello' ) // tentar converter uma string para número resulta em NaN
    • Verificação:
      • Função Number.isNaN()
      • Função Object.is() - compara se dois valores são iguais
      • Operador !== - NaN é o único valor no JavaScript que não é igual a ele mesmo
  • -0 - útil em operações com números de ponto-flutuante
    • 0 e -0 são valores diferentes. Apesar disso, 0 === -0 retorna true (valor incorreto).
      • A função Object.is() deve ser utilizada: Object.is(0, -0) é igual a false (valor esperado)

Recomendações de leitura

Referências

  1. Cálculo, volume I. James Stewart. 6ª ed. Pág.: 83.
  2. https://eloquentjavascript.net/01_values.html#p_rcMZUIfGYR