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.
- Os tipos
double(o padrão) efloat, e quantos dígitos cada um acerta. - Por que
0.1 + 0.2dá0.30000000000000004. - Os formatos do
printf:%f,%.2f,%ge%e. - A divisão inteira: por que
5 / 2dá2e5.0 / 2dá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
fno fim do número pede um literalfloat. Aqui,1234567.891tem 10 dígitos, e ofloatsó guardou1234567.875. - 1.0 / 3.0
- Um terço. O
floatacerta 7 casas (0.3333333) e erra a partir daí; odoubleacerta 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.
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
| Escrita | Tipo | Observação |
|---|---|---|
4.5, 0.1, 3.0 | double | Sempre com ponto, nunca com vírgula. |
3. ou .5 | double | Válido, mas 3.0 e 0.5 são mais fáceis de ler. |
4.5f | float | O f pede float. |
3.844e8 | double | Notação científica: 3,844 × 108 = 384400000. |
1e-9 | double | 1 × 10-9 = 0,000000001. Útil para tolerâncias (seção 5). |
3 | int (!) | 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
%fmostra 6 casas e arredonda:0.300000. Parece perfeito.%.2fmostra 2 casas:0.30.%.17fmostra 17 casas e revela o segredo:0.30000000000000004. O0.1e o0.2guardados já eram aproximações, e a soma das aproximações caiu num número vizinho do0.3guardado.soma == 0.3pergunta "são iguais?" e o C responde0(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).
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]
| Formato | O que faz | Bom para |
|---|---|---|
%f | Sempre 6 casas depois do ponto. Números muito pequenos viram 0.000000. | Quase nunca sozinho. |
%.2f | Exatamente 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. |
%e | Sempre notação científica: 3.844000e+08 = 3,844 × 108. | Ciência e engenharia. |
%8.2f | 2 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.
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
intdividido porint: resultadoint,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
doublepara a conta inteira ser feita emdouble:2.5. - double cada = total / pessoas;
- A armadilha: a caixa é
double, mas a conta à direita só temint. O C calcula primeiro (7 / 2=3) e só depois guarda o3na caixa, como3.0. Nenhum aviso. - (double) total / pessoas
- O
(double)na frente é uma conversão (cast): "tratetotalcomodoublenesta conta". Aí a divisão vira7.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 ofabssozinho; outras funções, comosqrtde uma variável, podem pedir o-lmno 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
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.
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.
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.
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
| Escrita | O 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-9 | Notaçã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 / 2 | 2 (divisão inteira) |
5.0 / 2, (double) a / b | Divisã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:
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; comf,float.- Muitos números, como 0,1, não cabem exatos em binário:
0.1 + 0.2dá0.30000000000000004em qualquer linguagem. printf:%f(6 casas),%.2f(2 casas, arredondando),%g(sem zeros inúteis),%e(científica). Noscanf,doubleé%lf.int / inté divisão inteira (5 / 2=2), mesmo que o resultado vá para umdouble. Um ladodoublebasta:5.0 / 2ou(double) a / b.- Nunca compare com
==: usefabs(a - b) < tolerancia. - Dividir
doublepor zero dáinfounan, sem travar.