C float e double

Altura, peso, preço, média de notas, temperatura: muita coisa tem vírgula. Em C, esses números moram nos tipos float e double, chamados de ponto flutuante. Eles são práticos, mas guardam uma surpresa que pega todo iniciante (e muito programador experiente): 0.1 + 0.2 não dá exatamente 0.3. Nesta aula você vai ver por quê, como mostrar esses números do jeito certo com o printf e como não cair nas armadilhas da divisão e da comparação.

Você vai aprender
  • Os tipos double (o padrão) e float, e quantos dígitos cada um acerta.
  • Por que 0.1 + 0.2 dá 0.30000000000000004.
  • Os formatos do printf: %f, %.2f, %g e %e.
  • A divisão inteira: por que 5 / 2 dá 2 e 5.0 / 2 dá 2.5.
  • Por que nunca comparar números com vírgula com ==, e como comparar com tolerância.

Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. Todas as saídas desta aula saíram iguais no editor do guia.


A ideia: a dízima que não cabe no papel

Tente escrever 1/3 em decimal numa folha: 0,33333…. Os 3 não acabam nunca. Em algum momento você para e aceita um valor quase certo: 0,3333.

O computador faz contas em binário (base 2), e em binário acontece a mesma coisa com números que, para nós, parecem simples: 0,1 em binário é uma dízima infinita (0,000110011001100…). Como a caixa tem um número fixo de bits, o computador guarda o valor mais próximo que cabe. Na maioria das vezes ninguém percebe; às vezes, a diferença aparece.

O nome "ponto flutuante" vem do jeito de guardar: um número como 384400000 vira algo como 3,844 × 108. A vírgula (o "ponto", em inglês) "flutua" para onde for preciso, e por isso o mesmo tipo guarda números enormes e minúsculos.


1. double e float

#include <stdio.h>

int main(void) {
    float f = 1234567.891f;
    double d = 1234567.891;
    float terco_f = 1.0f / 3.0f;
    double terco_d = 1.0 / 3.0;
    printf("float:  %.3f\n", f);
    printf("double: %.3f\n", d);
    printf("float:  %.20f\n", terco_f);
    printf("double: %.20f\n", terco_d);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra precisao.c -o precisao
$ ./precisao
float:  1234567.875      « o float errou já na 8ª casa
double: 1234567.891
float:  0.33333334326744079590
double: 0.33333333333333331483
double
"Precisão dupla": 8 bytes, acerta uns 15 a 16 dígitos. É o tipo padrão para números com vírgula em C: todo literal com ponto, como 1234567.891, já é double.
float … 1234567.891f
"Flutuante", precisão simples: 4 bytes, acerta uns 6 a 7 dígitos. O f no fim do número pede um literal float. Aqui, 1234567.891 tem 10 dígitos, e o float só guardou 1234567.875.
1.0 / 3.0
Um terço. O float acerta 7 casas (0.3333333) e erra a partir daí; o double acerta 16. Nenhum dos dois é exato: 1/3 não cabe em binário.
%.20f
Mostra 20 casas depois do ponto. Bom para "espiar" o valor guardado de verdade.
Qual usar: double

Use double sempre, a não ser que haja um bom motivo para float (milhões de números na memória, placas de vídeo, aparelhos pequenos). A economia de 4 bytes quase nunca compensa a perda de precisão. Existe também o long double (%Lf), com ainda mais precisão, raramente necessário.

Literais de ponto flutuante

EscritaTipoObservação
4.5, 0.1, 3.0doubleSempre com ponto, nunca com vírgula.
3. ou .5doubleVálido, mas 3.0 e 0.5 são mais fáceis de ler.
4.5ffloatO f pede float.
3.844e8doubleNotação científica: 3,844 × 108 = 384400000.
1e-9double1 × 10-9 = 0,000000001. Útil para tolerâncias (seção 5).
3int (!)Sem ponto, é inteiro. Isso importa na divisão (seção 4).

2. O famoso 0.1 + 0.2

#include <stdio.h>

int main(void) {
    double a = 0.1;
    double b = 0.2;
    double soma = a + b;
    printf("%f\n", soma);
    printf("%.2f\n", soma);
    printf("%.17f\n", soma);
    printf("%d\n", soma == 0.3);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra soma.c -o soma
$ ./soma
0.300000
0.30
0.30000000000000004      « o valor guardado de verdade
0                        « 0 = falso: soma não é igual a 0.3
  1. %f mostra 6 casas e arredonda: 0.300000. Parece perfeito.
  2. %.2f mostra 2 casas: 0.30.
  3. %.17f mostra 17 casas e revela o segredo: 0.30000000000000004. O 0.1 e o 0.2 guardados já eram aproximações, e a soma das aproximações caiu num número vizinho do 0.3 guardado.
  4. soma == 0.3 pergunta "são iguais?" e o C responde 0 (falso). O == é explicado em Operadores de Comparação.

Isso não é defeito do C. Python, JavaScript, Java e as planilhas fazem a mesma conta e chegam ao mesmo 0.30000000000000004, porque todos usam o mesmo padrão de ponto flutuante dos processadores (o IEEE 754).

E dinheiro?

Para aprender, double com %.2f serve bem. Mas sistemas que lidam com dinheiro de verdade guardam centavos num inteiro (long long preco_centavos = 499;), justamente para nunca perder um centavo em arredondamentos.


3. Mostrar com o printf: %f, %.2f, %g, %e

#include <stdio.h>

int main(void) {
    double preco = 4.5;
    double distancia = 384400000.0;
    double atomo = 0.0000000001;
    printf("%%f:   %f | %f | %f\n", preco, distancia, atomo);
    printf("%%.2f: %.2f | %.2f | %.2f\n", preco, distancia, atomo);
    printf("%%g:   %g | %g | %g\n", preco, distancia, atomo);
    printf("%%e:   %e | %e | %e\n", preco, distancia, atomo);
    printf("[%8.2f]\n", preco);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra formatos.c -o formatos
$ ./formatos
%f:   4.500000 | 384400000.000000 | 0.000000
%.2f: 4.50 | 384400000.00 | 0.00
%g:   4.5 | 3.844e+08 | 1e-10
%e:   4.500000e+00 | 3.844000e+08 | 1.000000e-10
[    4.50]
FormatoO que fazBom para
%fSempre 6 casas depois do ponto. Números muito pequenos viram 0.000000.Quase nunca sozinho.
%.2fExatamente 2 casas, arredondando. Troque o 2 por quantas quiser.Preços, médias, medidas: o mais usado.
%g"Geral": tira os zeros inúteis e passa para notação científica quando o número é muito grande ou muito pequeno.Mostrar um número sem saber o tamanho dele.
%eSempre notação científica: 3.844000e+08 = 3,844 × 108.Ciência e engenharia.
%8.2f2 casas, ocupando pelo menos 8 colunas (completa com espaços à esquerda).Alinhar tabelas.
%%Mostra um % de verdade.O %%f: do exemplo.

O %f serve tanto para double quanto para float no printf (o float é convertido para double no caminho). Todas as opções de largura e precisão estão em Formatos do printf.

Ponto, e não vírgula

O printf mostra 4.50, com ponto, mesmo num computador em português: o C usa o formato americano até que o programa peça outro com setlocale. E na leitura com scanf a letra muda: double é %lf (com l), e float é %f. Isso é explicado em Entrada com scanf.

Missão: o preço do pão de queijo aparece como Preço: 4.500000. Mude só o formato do printf para mostrar duas casas: Preço: 4.50.

#include <stdio.h>

int main(void) {
    double preco = 4.5;
    printf("Preço: %f\n", preco);
    return 0;
}
#include <stdio.h>

int main(void) {
    double preco = 4.5;
    printf("Preço: %.2f\n", preco);
    return 0;
}

4. Divisão inteira: 5 / 2 × 5.0 / 2

Em C, o tipo do resultado de uma conta depende do tipo dos números. Se os dois lados da divisão são inteiros, o resultado também é inteiro, e a parte depois da vírgula é jogada fora:

#include <stdio.h>

int main(void) {
    printf("5 / 2     = %d\n", 5 / 2);
    printf("5.0 / 2   = %f\n", 5.0 / 2);
    printf("5 / 2.0   = %f\n", 5 / 2.0);
    int total = 7;
    int pessoas = 2;
    double cada = total / pessoas;
    printf("cada (errado) = %.2f\n", cada);
    double cada2 = (double) total / pessoas;
    printf("cada (certo)  = %.2f\n", cada2);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra divisao.c -o divisao
$ ./divisao
5 / 2     = 2
5.0 / 2   = 2.500000
5 / 2.0   = 2.500000
cada (errado) = 3.00     « 7 / 2 foi feito em int (3) e só depois guardado no double
cada (certo)  = 3.50
5 / 2
int dividido por int: resultado int, 2. O resto (1) é jogado fora; para pegá-lo, existe o % (aula Operadores Aritméticos).
5.0 / 2 e 5 / 2.0
Basta um dos lados ser double para a conta inteira ser feita em double: 2.5.
double cada = total / pessoas;
A armadilha: a caixa é double, mas a conta à direita só tem int. O C calcula primeiro (7 / 2 = 3) e só depois guarda o 3 na caixa, como 3.0. Nenhum aviso.
(double) total / pessoas
O (double) na frente é uma conversão (cast): "trate total como double nesta conta". Aí a divisão vira 7.0 / 2 = 3.5. Detalhes em Conversão de Tipos.

Missão: a Ana e uma amiga dividiram um açaí de 7 reais. O programa diz que cada uma paga 3.00, e alguém está levando 1 real de graça! Conserte para mostrar Cada um paga: 3.50. Você pode mudar o tipo de uma variável ou usar (double).

#include <stdio.h>

int main(void) {
    int total = 7;
    int pessoas = 2;
    double cada = total / pessoas;
    printf("Cada um paga: %.2f\n", cada);
    return 0;
}
#include <stdio.h>

int main(void) {
    double total = 7.0;
    int pessoas = 2;
    double cada = total / pessoas;
    printf("Cada um paga: %.2f\n", cada);
    return 0;
}

A letra errada no printf

Como 5 / 2 é int e 5.0 / 2 é double, trocar a letra do printf dá lixo. O -Wall avisa:

$ cat formato_errado.c
#include <stdio.h>

int main(void) {
    printf("%f\n", 5 / 2);
    printf("%d\n", 5.0 / 2);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra formato_errado.c -o formato_errado
formato_errado.c: In function ‘main’:
formato_errado.c:4:14: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
    4 |     printf("%f\n", 5 / 2);
      |             ~^     ~~~~~
      |              |     |
      |              |     int
      |              double
      |             %d
formato_errado.c:5:14: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘double’ [-Wformat=]
    5 |     printf("%d\n", 5.0 / 2);
      |             ~^     ~~~~~~~
      |              |         |
      |              int       double
      |             %f
$ ./formato_errado
0.000000                 « nem 2, nem 2.5
0

O printf não converte nada: ele procura um double onde você mandou um int (e vice-versa) e lê o lugar errado da memória. Nesta máquina saiu 0.000000 e 0; em outra, pode sair outra coisa. Conserto: a letra combina com o tipo da conta, não com o que você queria que ela fosse.


5. Comparar números com vírgula

Pequenos erros se acumulam. Somar 0.1 dez vezes deveria dar 1.0:

#include <stdio.h>

int main(void) {
    double total = 0.0;
    for (int i = 0; i < 10; i++) {
        total = total + 0.1;
    }
    printf("%.17f\n", total);
    printf("%d\n", total == 1.0);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra conta.c -o conta
$ ./conta
0.99999999999999989
0                        « total == 1.0 é falso

(O for repete a soma 10 vezes; aula for.) Um programa que esperasse total == 1.0 para parar nunca pararia. A solução é perguntar "estão perto o bastante?" em vez de "são iguais?": a distância entre os dois números tem de ser menor que uma tolerância pequena.

#include <stdio.h>
#include <math.h>

int main(void) {
    double soma = 0.1 + 0.2;
    double tolerancia = 1e-9;
    printf("soma == 0.3: %d\n", soma == 0.3);
    printf("perto de 0.3: %d\n", fabs(soma - 0.3) < tolerancia);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra compara.c -o compara
$ ./compara
soma == 0.3: 0
perto de 0.3: 1          « 1 = verdadeiro
#include <math.h>
Traz as funções de matemática (aula Matemática). Este exemplo compilou no gcc sem -lm, porque o gcc resolve o fabs sozinho; outras funções, como sqrt de uma variável, podem pedir o -lm no fim da linha do gcc.
soma - 0.3
A diferença: um número minúsculo, positivo ou negativo.
fabs(…)
Floating absolute, "valor absoluto de ponto flutuante": tira o sinal de menos. A distância entre dois números nunca é negativa.
< tolerancia
"Menor que 0,000000001?" Se sim, para efeitos práticos são iguais.

Qual tolerância usar depende do problema: 1e-9 é bom para números perto de 1; para dinheiro em reais, meio centavo (0.005) já basta. Comparar com < e > (maior, menor) continua valendo normalmente: o problema é só o == e o !=.

Dividir por zero não trava (com double)

Com inteiros, dividir por zero derruba o programa (aula Operadores Aritméticos). Com double, o padrão IEEE 754 tem valores especiais para isso:

$ cat zero.c
#include <stdio.h>

int main(void) {
    double x = 1.0;
    double zero = 0.0;
    printf("%f %f %f\n", x / zero, -x / zero, zero / zero);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra zero.c -o zero
$ ./zero
inf -inf -nan

inf é "infinito", e nan (not a number) é "não é um número", o resultado de 0/0. Um nan contamina todas as contas seguintes e nunca é igual a nada, nem a ele mesmo. Se ele aparecer na sua saída, procure uma divisão por zero ou uma conta impossível, como a raiz de um número negativo.


Quando dá errado

A média saiu 3.00 em vez de 3.50

O que significa: divisão inteira: os dois lados da divisão eram int. O gcc não avisa.

Como consertar: faça um dos lados ser double: uma variável double, um literal com ponto (soma / 2.0) ou (double) soma / quantidade.

warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘double’

O que significa: a letra do printf não combina com o tipo do valor. O programa mostra lixo.

Como consertar: double e float usam %f (ou %.2f, %g, %e); int usa %d. O gcc sugere a letra certa na última linha do aviso.

Um if (x == 0.3) nunca é verdadeiro

O que significa: o valor calculado é vizinho de 0.3, mas não exatamente igual.

Como consertar: compare com tolerância: fabs(x - 0.3) < 1e-9. Para ver o valor real, mostre com %.17f.

O float mostra 1234567.875 em vez de 1234567.891

O que significa: o float só acerta uns 7 dígitos. Com -Wconversion, o gcc até avisa quando um literal double perde valor ao entrar num float: conversion from ‘double’ to ‘float’ changes value from ‘4.9900000000000002e+0’ to ‘4.98999977e+0f’ [-Wfloat-conversion].

Como consertar: use double.

Saiu inf ou nan

O que significa: divisão por zero (inf) ou uma conta sem resultado, como 0.0 / 0.0 (nan).

Como consertar: confira o divisor antes de dividir.


Referência rápida

EscritaO que faz
double x = 4.5;Número com vírgula, ~15 dígitos certos (o padrão)
float y = 4.5f;Precisão simples, ~7 dígitos certos
3.844e8, 1e-9Notação científica
printf("%f", x)6 casas: 4.500000
printf("%.2f", x)2 casas, arredondando: 4.50
printf("%g", x)Sem zeros inúteis: 4.5, 3.844e+08
printf("%e", x)Científica: 4.500000e+00
printf("%8.2f", x)2 casas, pelo menos 8 colunas
5 / 22 (divisão inteira)
5.0 / 2, (double) a / bDivisão com vírgula: 2.5
fabs(a - b) < 1e-9"a e b são praticamente iguais" (math.h)
scanf("%lf", &x)Ler um double (com l!)

Exercícios

1. Duas casas

Complete o formato para mostrar media com duas casas depois do ponto:

printf("Média: \n", media);

2. Divisão inteira

Quanto vale 9 / 2 em C?

3. O que printf("%d", 0.1 + 0.2 == 0.3); mostra?

4. Com int a = 7, b = 2;, qual expressão dá 3.5?

5. Qual tipo você deve usar, na dúvida, para números com vírgula?

6. O que printf("%g\n", 384400000.0); mostra?

Missão final: dez moedas de dez centavos

Missão: a Ana juntou dez moedas de 10 centavos. O programa soma tudo, mostra Total: 1.00, mas diz que o total não é igual a 1 real (Igual a 1 real: 0). Troque a comparação com == por uma comparação com tolerância, usando fabs (já incluímos o math.h), para mostrar Igual a 1 real: 1.

#include <stdio.h>
#include <math.h>

int main(void) {
    double total = 0.0;
    for (int i = 0; i < 10; i++) {
        total = total + 0.1;
    }
    printf("Total: %.2f\n", total);
    printf("Igual a 1 real: %d\n", total == 1.0);
    return 0;
}
#include <stdio.h>
#include <math.h>

int main(void) {
    double total = 0.0;
    for (int i = 0; i < 10; i++) {
        total = total + 0.1;
    }
    printf("Total: %.2f\n", total);
    printf("Igual a 1 real: %d\n", fabs(total - 1.0) < 1e-9);
    return 0;
}

Repare que o %.2f mostrou 1.00 o tempo todo: o arredondamento da tela esconde a diferença. Por isso o que vale é a comparação, não o que aparece na tela.


Resumo

  • double (8 bytes, ~15 dígitos) é o tipo padrão para números com vírgula; float (4 bytes, ~7 dígitos) só quando houver motivo. Literal com ponto é double; com f, float.
  • Muitos números, como 0,1, não cabem exatos em binário: 0.1 + 0.2 dá 0.30000000000000004 em qualquer linguagem.
  • printf: %f (6 casas), %.2f (2 casas, arredondando), %g (sem zeros inúteis), %e (científica). No scanf, double é %lf.
  • int / int é divisão inteira (5 / 2 = 2), mesmo que o resultado vá para um double. Um lado double basta: 5.0 / 2 ou (double) a / b.
  • Nunca compare com ==: use fabs(a - b) < tolerancia.
  • Dividir double por zero dá inf ou nan, sem travar.