A imprecisão dos números (com JavaScript)

Rafael Lagemann
4 min readFeb 13, 2018

--

Em JavaScript, podemos representar números da seguinte forma:

42       // inteiro, número 42 na base 10
0x2A // 42 em base 16 (hexadecimal)
0o52 // 42 em base 8 (octal)
0b101010 // 42 em base 2 (binário)

Apesar de podermos representá-los dessas maneiras, todos os números em JavaScript são floats.

Números em ponto flutuante se parecem com o seguinte:

3.14     // temos a parte inteira(3) o ponto e a parte decimal(14)

Utilizando notação científica:

1.5e4

Caso não esteja familiarizado, agora temos a letra ‘e’ para número em notação científica, isto é, multiplicado por uma potência de base 10 com expoente 4. 1,5 * 10⁴ === 15000

Em algumas linguagens, como C, devemos declarar o tipo de número que queremos. Exemplo:

int a = 42;
float b = 42.1;
double c = 42.5;

O número na máquina

A representação binária possui limitações. Não temos como representar toda a precisão necessária que alguns números exigem quando os transportamos para um espaço discretizado e binário, como o computador.

Erros de precisão ocorrem em qualquer linguagem de programação, pois são originadas da necessidade da representação dos números em binário.

Na nossa máquina, os floats do JavaScript estão armazenados como binários de 64 bits. Vejamos exemplos com JavaScript.

Os números inteiros podem ter até 15 dígitos de precisão:

let x = 999999999999999   // x é 999999999999999
let y = 9999999999999999 // y é 10000000000000000

Podemos ver, também, que a aritmética com ponto flutuante nem sempre será precisa:

let x = 0.1 + 0.1 + 0.1  // x é 0.30000000000000004

Isso acontece pois apenas frações cujo denominador é uma potência de 2 podem ser representados corretamente em binário. Como 0.1 é 1/10, e 10 não é um potência de 2, o computador precisa adequar-se e arredondar de acordo com o número de bits que tem disponível para isso. A precisão pode ser de 10, 23 ou 52 bits, o que vai fazer com o que o resultado seja diferente, porém sempre incorreto.

Muitas vezes, não precisamos nos preocupar com estes pequenos erros de precisão. Mas em alguns casos, sim. A imprecisão dos números floats representados na máquina é o motivo pelo qual muitos programadores não utilizam um único número float para representar valores monetários. Por exemplo: R$50,30 seria um inteiro para os 50 reais e outro inteiro para os 30 centavos. Evitando aritmética entre números de ponto flutuante.

O maior inteiro

Para poupar o leitor, não entraremos em detalhes sobre a organização dos bits que são utilizados na representação binária do número. De forma geral, define-se 53 bits(dos 64bits disponíveis) para a magnitude. Você pode saber mais acessando este excelente link e este outro.

Então para obter o maior número inteiro, temos:

Math.pow(2, 53)   // 9007199254740992

No entanto, se você acessar esta constante:

Number.MAX_SAFE_INTEGER // 9007199254740991

Quer dizer que Math.pow(2, 53) não é um número seguro?

De acordo com a especificação da linguagem, Number.MAX_SAFE_INTEGER é o maior número n, tal que n e n + 1 sejam exatamente representáveis com um valor Number.

Vejamos então porque Math.pow(2, 53) não é um número seguro:

Math.pow(2, 53)      // 9007199254740992
Math.pow(2, 53) + 1 // 9007199254740992

Ou seja, se Math.pow(2, 53) fosse n, não teríamos uma representação correta para n + 1. Imagine que você está utilizando um laço for que sempre soma um ao valor de iteração. Quando atingir 9007199254740992 o laço ficará somando 1 indefinidamente e sempre obtendo o mesmo valor, entrando em loop infinito.

Mas ainda não chegamos no maior número

De fato, o maior número está aqui:

Number.MAX_VALUE // 1.7976931348623157e+308

No entanto, muitos número entre Number.MAX_SAFE_INTEGER e Number.MAX_VALUE não podem ser representados de forma segura. Por exemplo:

Math.pow(2, 53) + 3 // 9007199254740996
Math.pow(2, 53) + 4 // 9007199254740996

Estes valores não atendem a definição de número seguro visto acima.

Por hoje é só

Pessoal, este é um assunto cheio de detalhes técnicos que foram omitidos para tornar o texto mais viável e evitar algumas caneladas.

Para quem se interessar, deixo os links onde podem procurar por informações mais aprofundadas. Para ficar ninja no assunto, livros de Cálculo Numérico Computacional abordam o problema de forma mais teórica e com provas matemáticas. Esta postagem tem o objetivo de ser mais prática.

Fontes e leituras

--

--