Java double e float
Altura, peso, média de notas, temperatura: muitos números do dia a dia têm casas decimais. Para eles, o Java tem dois tipos, double e float. Eles são práticos, mas têm surpresas que todo programador precisa conhecer, como a conta 0.1 + 0.2 que não dá exatamente 0.3.
- Como declarar e usar
double(o padrão) efloat(com o sufixof). - Por que decimais são aproximados e o que isso causa.
- Como o Java mostra números muito grandes e muito pequenos (
1.0E10). - O que são
InfinityeNaN. - Como comparar dois decimais do jeito certo e quando trocar por
BigDecimal.
double: o tipo decimal padrão
Em Java, a parte decimal é separada por ponto, como em inglês: 1.65, nunca 1,65. Todo número com ponto escrito no código é um double:
public class Main {
public static void main(String[] args) {
double altura = 1.65;
double preco = 9.90;
double inteiro = 10;
double negativo = -273.15;
System.out.println(altura);
System.out.println(preco);
System.out.println(inteiro);
System.out.println(negativo);
System.out.println(10 / 4);
System.out.println(10.0 / 4);
}
}
$ javac Main.java && java Main 1.65 9.9 « zeros no fim não são guardados 10.0 « um int guardado em double ganha ".0" -273.15 2 « 10 / 4 com dois inteiros: divisão inteira 2.5 « com um double na conta, o resultado tem decimais
| Linha | O que mostra |
|---|---|
double preco = 9.90; | O double guarda o número 9,9; o zero à direita não é informação. Para mostrar 9,90, formate na hora de imprimir (veja abaixo). |
double inteiro = 10; | Um inteiro cabe num double sem problema (conversão automática). Ao imprimir, o Java sempre mostra pelo menos uma casa: 10.0. |
10 / 4 e 10.0 / 4 | Se os dois números da divisão são inteiros, a divisão é inteira e o .5 some. Basta um double na conta para o resultado ser double. Detalhes em Operadores Aritméticos. |
Double quer dizer "duplo": ele tem o dobro de bits do float (64 contra 32) e por isso guarda cerca de 15 a 16 dígitos significativos, contra 6 a 7 do float. O nome técnico dos dois é ponto flutuante (floating point): o ponto decimal "flutua", isto é, o mesmo tipo guarda tanto 0.000001 quanto 1000000.0.
Missão: a média de 7 e 8 deveria ser 7.5, mas o programa mostra 7.0. A conta (7 + 8) / 2 é feita só com inteiros. Mude um número para que a divisão seja decimal.
public class Main {
public static void main(String[] args) {
double media = (7 + 8) / 2;
System.out.println(media);
}
}
public class Main {
public static void main(String[] args) {
double media = (7 + 8) / 2.0;
System.out.println(media);
}
}
float e o sufixo f
O float ocupa metade da memória do double e é menos preciso. A primeira tentativa de usá-lo costuma ser esta:
public class Main {
public static void main(String[] args) {
float temperatura = 36.6;
}
}
$ javac Main.java
Main.java:3: error: incompatible types: possible lossy conversion from double to float
float temperatura = 36.6;
^
1 error
"Possível conversão com perda, de double para float". Como todo número com ponto é double, o 36.6 é um double, e colocá-lo num float (menor) pode perder precisão. O Java não faz isso sem você pedir. Para escrever um número float, coloque f (ou F) no fim:
public class Main {
public static void main(String[] args) {
float f = 1.0f / 3;
double d = 1.0 / 3;
System.out.println(f);
System.out.println(d);
float temperatura = 36.6f;
System.out.println(temperatura);
double dd = temperatura;
System.out.println(dd);
}
}
$ javac Main.java && java Main 0.33333334 « float: 8 dígitos, o último já errado 0.3333333333333333 « double: 16 dígitos 36.6 36.599998474121094 « o mesmo float visto "de perto" como double
A última linha revela um segredo: o float nunca guardou exatamente 36,6, e sim o número mais próximo que ele consegue representar. Impresso como float, o Java arredonda e mostra 36.6; convertido para double, o erro aparece. A próxima seção explica por quê.
Qual usar?
double | float | |
|---|---|---|
| Tamanho | 64 bits | 32 bits |
| Precisão | ~15–16 dígitos | ~6–7 dígitos |
| Como escrever | 36.6 (ou 36.6d) | 36.6f |
| Quando usar | Sempre, por padrão. Todos os métodos de Math trabalham com double. | Só quando uma biblioteca exige (jogos, gráficos 3D) ou para guardar milhões de valores economizando memória. |
Decimais são aproximados: 0.1 + 0.2
Esta é a conta mais famosa da programação:
public class Main {
public static void main(String[] args) {
System.out.println(0.1 + 0.2);
System.out.println(1.10 - 1.00);
System.out.println(0.1 + 0.2 == 0.3);
double soma = 0;
soma = soma + 0.1;
soma = soma + 0.1;
soma = soma + 0.1;
System.out.println(soma);
System.out.println(0.1f + 0.2f);
System.out.println(3 * 0.1);
}
}
$ javac Main.java && java Main 0.30000000000000004 0.10000000000000009 false « 0.1 + 0.2 NÃO é igual a 0.3 0.30000000000000004 0.3 « float: a imprecisão existe, mas some no arredondamento 0.30000000000000004
Não é defeito do Java: Python, JavaScript, C e quase todas as linguagens dão o mesmo resultado. O motivo é que o computador guarda os números em binário (base 2), e em binário o número 0,1 é uma dízima infinita, como 1/3 = 0,3333… em decimal. Com espaço limitado, ele guarda uma aproximação muito, muito próxima de 0,1, mas não exatamente 0,1. Somando duas aproximações, o errinho aparece no 17º dígito.
Pense em dividir uma pizza em 3 fatias "iguais" usando uma régua que só marca milímetros: cada fatia fica com 33 mm em vez de 33,333… mm, e juntando as três falta 1 mm. Para medidas, um erro na 17ª casa não importa. Para dinheiro e para comparações com ==, importa.
double para dinheiro
1.10 - 1.00 deu 0.10000000000000009. Num sistema de loja, somas assim, repetidas milhares de vezes, criam centavos que não existem. Para valores monetários, use a classe BigDecimal, que faz contas decimais exatas: veja BigDecimal e Dinheiro. Outra saída comum é guardar os valores em centavos num long (long precoEmCentavos = 990;).
Mostrando com menos casas
Quando o problema é só a aparência, formate ao imprimir. System.out.printf("%.2f%n", valor) mostra o número com duas casas, arredondado. Na máquina da Ana, configurada em português do Brasil, o separador decimal impresso é a vírgula:
Assim, System.out.printf("%.2f%n", 0.1 + 0.2); mostra 0,30 (a saída real está no programa da próxima seção). O %.2f quer dizer "número decimal com 2 casas" e o %n pula a linha. O printf e suas regras estão em Formatar Texto.
Comparar decimais
Como as contas acumulam pequenos erros, não compare decimais com ==. Pergunte se a diferença entre eles é pequena o bastante:
public class Main {
public static void main(String[] args) {
double a = 0.1 + 0.2;
double b = 0.3;
System.out.println(a == b);
System.out.println(Math.abs(a - b) < 0.000001);
System.out.println(Math.abs(a - b));
System.out.printf("%.2f%n", a);
}
}
- Math.abs(
- Valor absoluto: tira o sinal de menos (a diferença pode dar negativa).
- a - b
- A diferença entre os dois números.
- )
- Fecha o
abs. - < 0.000001
- A tolerância: "se a diferença for menor que um milionésimo, considero iguais". Escolha uma tolerância adequada ao problema.
$ javac Main.java && java Main false true 5.551115123125783E-17 0,30
O == deu false, a comparação com tolerância deu true, e a diferença real era de 0,0000000000000000555 (5.55…E-17). Os operadores == e < são explicados em Operadores de Comparação.
Missão: a balança da loja somou três pesagens de 0.1 kg. O programa pergunta se o total é 0.3 kg e responde false. Troque o == por uma comparação com tolerância usando Math.abs para que ele mostre true.
public class Main {
public static void main(String[] args) {
double total = 0.1 + 0.1 + 0.1;
System.out.println(total == 0.3);
}
}
public class Main {
public static void main(String[] args) {
double total = 0.1 + 0.1 + 0.1;
System.out.println(Math.abs(total - 0.3) < 0.000001);
}
}
Notação científica: 1.0E10
Números muito grandes ou muito pequenos têm zeros demais. A ciência os escreve como "1,5 × 1011". O Java escreve 1.5E11: o E (de expoente) quer dizer "vezes 10 elevado a". Você pode usar essa forma no código e o Java também a usa ao imprimir:
public class Main {
public static void main(String[] args) {
double distancia = 1.5e11;
double atomo = 1e-10;
System.out.println(distancia);
System.out.println(atomo);
System.out.println(1e10);
System.out.println(1234567.0);
System.out.println(12345678.9);
System.out.println(0.001);
System.out.println(0.0001);
System.out.println(100.0 * 100000);
}
}
$ javac Main.java && java Main 1.5E11 « 1,5 × 10¹¹ = 150.000.000.000 1.0E-10 « 1 × 10⁻¹⁰ = 0,0000000001 1.0E10 1234567.0 « abaixo de 10 milhões: escrita normal 1.23456789E7 « a partir de 10 milhões: notação científica 0.001 « a partir de 0,001: escrita normal 1.0E-4 « abaixo de 0,001: notação científica 1.0E7
| No código ou na saída | Lê-se | Valor |
|---|---|---|
1.5E11 ou 1.5e11 | 1,5 × 1011 | 150.000.000.000 |
1.0E-4 | 1 × 10-4 | 0,0001 |
1.23456789E7 | 1,23456789 × 107 | 12.345.678,9 |
A regra do println para double: números de 0,001 até menos de 10.000.000 aparecem do jeito normal; fora dessa faixa, em notação científica. Isso costuma assustar quem calcula, por exemplo, o faturamento de uma empresa: 1.0E7 é simplesmente dez milhões. Para mostrar sempre do jeito normal, use printf (Formatar Texto).
Infinity e NaN
Dividir um inteiro por zero faz o programa parar com ArithmeticException (veja Operadores Aritméticos). Com double, não: o padrão dos decimais tem valores especiais para esses casos:
public class Main {
public static void main(String[] args) {
System.out.println(1.0 / 0);
System.out.println(-1.0 / 0);
System.out.println(0.0 / 0);
System.out.println(Math.sqrt(-4));
double nada = 0.0 / 0;
System.out.println(nada == nada);
System.out.println(Double.isNaN(nada));
System.out.println(Double.isInfinite(1.0 / 0));
System.out.println(Double.MAX_VALUE * 2);
System.out.println(Double.MIN_VALUE);
System.out.println(1 / 0.0 + 5);
}
}
$ javac Main.java && java Main Infinity « 1.0 / 0: infinito positivo -Infinity « infinito negativo NaN « 0.0 / 0: "não é um número" NaN « raiz quadrada de número negativo false « NaN não é igual nem a si mesmo! true true Infinity « passar do maior double vira infinito (não dá a volta) 4.9E-324 « o menor double POSITIVO, não o mais negativo Infinity « infinito + 5 continua infinito
| Valor | Significado | Aparece quando | Como testar |
|---|---|---|---|
Infinity | Infinito positivo | Número positivo ÷ 0.0; conta passa de Double.MAX_VALUE | Double.isInfinite(x) |
-Infinity | Infinito negativo | Número negativo ÷ 0.0 | Double.isInfinite(x) |
NaN | Not a Number, "não é um número" | 0.0 / 0, Math.sqrt(-4), contas com um NaN | Double.isNaN(x) (nunca x == x) |
O perigo desses valores é que eles não param o programa: um NaN entra numa conta, contamina o resultado, e você só descobre lá no fim, quando a média dos alunos aparece como NaN. Se uma divisão pode ter divisor zero, confira antes com um if.
Double.MIN_VALUE não é o "mínimo"
Diferente de Integer.MIN_VALUE (o inteiro mais negativo), Double.MIN_VALUE é o menor número positivo que um double representa: 4,9 × 10-324. O double mais negativo é -Double.MAX_VALUE.
Quando dá errado
$ javac Main.java
Main.java:3: error: incompatible types: possible lossy conversion from double to float
float temperatura = 36.6;
^
1 error
O que quer dizer: 36.6 é um double e não entra num float sem perda.
Como consertar: escreva 36.6f, ou, melhor ainda, use double. O mesmo erro com int (int x = 2.5;, "from double to int") se resolve trocando o tipo para double ou fazendo um casting.
public class Main {
public static void main(String[] args) {
double preco = 9,90;
}
}
$ javac Main.java
Main.java:3: error: <identifier> expected
double preco = 9,90;
^
1 error
O que quer dizer: a vírgula separa variáveis numa declaração (int a = 1, b = 2;). O Java entendeu preco = 9, depois uma vírgula, e esperava o nome (identificador) de outra variável; achou 90, que não pode ser nome.
Como consertar: no código, sempre ponto: 9.90. A vírgula só aparece na saída formatada para o Brasil.
7.0 em vez de 7.5
O que aconteceu: double media = (7 + 8) / 2; faz a divisão com dois inteiros (resultado 7) e só depois guarda no double, que vira 7.0. Guardar em double não muda o tipo da conta.
Como consertar: faça pelo menos um número da divisão ser decimal: / 2.0. Com variáveis inteiras, use um casting: (double) soma / 2.
NaN ou Infinity no resultado
O que aconteceu: em algum ponto houve uma divisão por zero com double (por exemplo, a média de uma turma sem alunos: 0.0 / 0) ou uma operação impossível, como a raiz de um negativo.
Como consertar: procure as divisões e confira o divisor antes de dividir. Para descobrir de onde veio, imprima os valores intermediários ou use Double.isNaN e Double.isInfinite.
Referência
| Escrita / método | O que é | Exemplo → resultado |
|---|---|---|
1.65 | Literal double | double h = 1.65; |
36.6f | Literal float | float t = 36.6f; |
1.5e11 | Notação científica (× 1011) | 1.5E11 |
Double.MAX_VALUE | Maior double | 1.7976931348623157E308 |
Double.MIN_VALUE | Menor double positivo | 4.9E-324 |
Double.isNaN(x) | x é NaN? | Double.isNaN(0.0 / 0) → true |
Double.isInfinite(x) | x é ±infinito? | Double.isInfinite(1.0 / 0) → true |
Math.abs(a - b) < tol | Comparar decimais com tolerância | Math.abs(0.1 + 0.2 - 0.3) < 1e-6 → true |
printf("%.2f%n", x) | Mostrar com 2 casas | 0,30 (locale pt_BR) |
BigDecimal | Decimal exato (dinheiro) | Veja BigDecimal e Dinheiro |
Exercícios
1. O sufixo do float
Complete para o código compilar:
2. Divisão decimal
Complete o divisor para resultado valer 3.5:
3. Testar NaN
Complete para descobrir se media é NaN:
4. O que System.out.println(0.1 + 0.2); mostra?
5. O que System.out.println(1.0 / 0); mostra?
6. Qual tipo usar para o preço de um produto num sistema de vendas?
7. O Java imprimiu 2.5E7. Que número é esse?
Resumo
doubleé o tipo decimal padrão; todo número com ponto no código édouble. Use ponto, nunca vírgula.floaté menor e menos preciso; precisa do sufixof(36.6f), senão:possible lossy conversion from double to float.- Decimais são aproximados:
0.1 + 0.2dá0.30000000000000004. Não use==com eles; compare comMath.abs(a - b) < tolerância. - Dinheiro:
BigDecimalou centavos emlong. - Fora da faixa de 0,001 a 10 milhões, o
printlnusa notação científica:1.0E7= 10.000.000. - Dividir
doublepor zero dáInfinityouNaN, sem erro.NaNnão é igual nem a si mesmo: useDouble.isNaN.