CodeGym /Cursos /JAVA 25 SELF /Problemas de precisão e valores especiais

Problemas de precisão e valores especiais

JAVA 25 SELF
Nível 6 , Lição 3
Disponível

1. Introdução

Pode parecer que, se o computador é “inteligente”, então 0.1 + 0.2 deveria ser simplesmente 0.3. Mas não é bem assim. Vamos entender com um exemplo simples.


double x = 0.1;
double y = 0.2;
double sum = x + y;
System.out.println(sum); // O que o programa vai imprimir?
Soma de números de ponto flutuante: esperamos 0.3, mas o que sai de fato?

Agora tente comparar com 0.3:

System.out.println(sum == 0.3); // Aqui será true ou false?

Se você vir false, não se surpreenda!

A razão está na representação dos números na memória

Computadores trabalham com números em sistema binário. Mas nem todas as frações decimais podem ser representadas como uma fração binária finita, assim como 1/3 não pode ser escrito exatamente em decimal (0.333...). Por exemplo, 0.1 no sistema binário — é uma fração infinita e precisa ser “arredondada” para caber no armazenamento.

Em termos simples, o double às vezes “finge” armazenar seu número com exatidão, mas na realidade guarda apenas um valor aproximado muito próximo.

2. Quais “pegadinhas” aparecem na aritmética com double?

Vamos ver alguns exemplos práticos.

Exemplo 1. A clássica “mágica” 0.1 + 0.2

double a = 0.1;
double b = 0.2;
double sum = a + b;
System.out.println(sum);            // 0.30000000000000004
System.out.println(sum == 0.3);     // false

O computador não imprimiu 0.3, mas 0.30000000000000004. A diferença é pequena, mas se você lida, por exemplo, com finanças — isso já é crítico.

Exemplo 2. Soma iterativa

double result = 0;
for (int i = 0; i < 10; i++)
{
    result += 0.1;
}
System.out.println(result); // 0.9999999999999999

Você queria 1.0 — acabou com um pouquinho menos. De novo, a razão é o arredondamento interno do double.

Por que isso é importante em problemas reais

Muitos pensam: “Qual é o problema? Um errinho pequeno, e daí!” Vamos ver um exemplo do mundo dos pagamentos.

Suponha que seu internet banking some 100 transações de 0.1 euro cada. Se o seu programa “perde” um centésimo de milésimo de euro em cada iteração, na escala do banco você já estará “perdendo” dinheiro de verdade. Aí o contador vai chegar até você e perguntar: “Onde está o nosso dinheiro?!”

3. Como comparar números de ponto flutuante corretamente

Como o double muitas vezes não consegue armazenar exatamente o valor que você espera, uma comparação direta com == pode te enganar. Em vez disso, costuma-se comparar o módulo da diferença com um número muito pequeno (epsilon).

Exemplo de comparação com tolerância

double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 0.000001;

if (Math.abs(a - b) < epsilon)
{
    System.out.println("Quase igual!"); // É mais seguro comparar assim
}

Aqui estamos dizendo: “Se a diferença entre os números for menor que um milionésimo, consideramos que os números são iguais”.
Observação: a função Math.abs(value) retorna o valor absoluto (módulo) do número passado para ela.

4. Valores especiais de double: Infinity, NaN, -Infinity

O tipo double armazena não apenas números, mas também valores especiais. Eles surgem em situações que, em matemática, seriam terminantemente proibidas.

Infinito (Infinity)

O que acontece se dividirmos 1 por 0?

double result = 1.0 / 0.0;
System.out.println(result); // Infinity

Em Java (e em muitas linguagens), a divisão por 0 para double não lança exceção! Em vez disso, o resultado se torna o valor especial “infinito positivo”.

Menos infinito (-Infinity)

Se você dividir um número negativo por 0, obterá o infinito negativo:

double result = -1.0 / 0.0;
System.out.println(result); // -Infinity

“Não é um número” (NaN — Not a Number)

Se você fizer algo fora do comum, por exemplo, tentar calcular a raiz quadrada de um número negativo:

double result = Math.sqrt(-1);
System.out.println(result); // NaN

Ou o resultado de dividir 0.0 / 0.0:

double result = 0.0 / 0.0;
System.out.println(result); // NaN

NaN é “tudo o que não seria um número na vida real”.

Verificando valores especiais

Em Java há funções para verificar esses valores:

System.out.println(Double.isInfinite(result));    // true se for infinito
System.out.println(Double.isNaN(result));         // true se for NaN

Tabela: Como o double reage a operações incomuns

Operação Resultado O que fica armazenado em double
1.0 / 0.0
Infinity +∞
-1.0 / 0.0
-Infinity -∞
0.0 / 0.0
NaN Não é um número
Math.sqrt(-1)
NaN Não é um número

5. Erros comuns ao trabalhar com números de ponto flutuante

Erro nº 1: Comparar números fracionários com ==
A armadilha mais comum: tentar verificar se dois números de ponto flutuante calculados são iguais usando uma comparação comum. Por causa do acúmulo de erros de arredondamento, você quase sempre terá um resultado inesperado. Sempre use comparação com tolerância (epsilon).

Erro nº 2: NaN e Infinity inesperados nos cálculos
Se você não controlar divisões por zero ou raízes de números negativos, podem aparecer NaN ou Infinity e “contaminar” todos os cálculos subsequentes. Não se esqueça de verificar valores suspeitos com Double.isNaN() e Double.isInfinite() — isso ajuda a evitar surpresas desagradáveis.

Erro nº 3: Usar NaN como “marcador”
Alguns iniciantes usam NaN como um valor “especial” para sinalizar, por exemplo, “se não encontrar — devolva NaN”. Mas lembre-se: NaN é traiçoeiro, e a comparação com == não vai funcionar! Verifique apenas com métodos especiais.

Erro nº 4: Esperar uma exceção ao dividir por 0.0
Ao contrário da divisão de inteiros, a divisão por 0.0 para double não lança erro, mas retorna Infinity ou NaN. Isso pode levar a bugs silenciosos e difíceis de detectar — o resultado existe, mas não é o que você esperava.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION