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.

Você vai aprender
  • Como declarar e usar double (o padrão) e float (com o sufixo f).
  • 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 Infinity e NaN.
  • 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
LinhaO 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 / 4Se 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?

doublefloat
Tamanho64 bits32 bits
Precisão~15–16 dígitos~6–7 dígitos
Como escrever36.6 (ou 36.6d)36.6f
Quando usarSempre, 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.

Nunca use 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ídaLê-seValor
1.5E11 ou 1.5e111,5 × 1011150.000.000.000
1.0E-41 × 10-40,0001
1.23456789E71,23456789 × 10712.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
ValorSignificadoAparece quandoComo testar
InfinityInfinito positivoNúmero positivo ÷ 0.0; conta passa de Double.MAX_VALUEDouble.isInfinite(x)
-InfinityInfinito negativoNúmero negativo ÷ 0.0Double.isInfinite(x)
NaNNot a Number, "não é um número"0.0 / 0, Math.sqrt(-4), contas com um NaNDouble.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

"possible lossy conversion from double to float"
$ 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.

Vírgula no lugar do ponto: "<identifier> expected"
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.

A média deu 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.

Apareceu 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étodoO que éExemplo → resultado
1.65Literal doubledouble h = 1.65;
36.6fLiteral floatfloat t = 36.6f;
1.5e11Notação científica (× 1011)1.5E11
Double.MAX_VALUEMaior double1.7976931348623157E308
Double.MIN_VALUEMenor double positivo4.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) < tolComparar decimais com tolerânciaMath.abs(0.1 + 0.2 - 0.3) < 1e-6 → true
printf("%.2f%n", x)Mostrar com 2 casas0,30 (locale pt_BR)
BigDecimalDecimal exato (dinheiro)Veja BigDecimal e Dinheiro

Exercícios

1. O sufixo do float

Complete para o código compilar:

float peso = 62.5;

2. Divisão decimal

Complete o divisor para resultado valer 3.5:

double resultado = 7 / ;

3. Testar NaN

Complete para descobrir se media é NaN:

boolean invalida = Double.(media);

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 sufixo f (36.6f), senão: possible lossy conversion from double to float.
  • Decimais são aproximados: 0.1 + 0.2 dá 0.30000000000000004. Não use == com eles; compare com Math.abs(a - b) < tolerância.
  • Dinheiro: BigDecimal ou centavos em long.
  • Fora da faixa de 0,001 a 10 milhões, o println usa notação científica: 1.0E7 = 10.000.000.
  • Dividir double por zero dá Infinity ou NaN, sem erro. NaN não é igual nem a si mesmo: use Double.isNaN.