Ponto flutuante
Esta aula aborda o uso de tipos de ponto flutuante em C, incluindo float e double, precisão, notação científica e armadilhas comuns em comparações. O aluno aprenderá a declarar, inicializar e comparar números de ponto flutuante corretamente, evitando erros de precisão.
Números de ponto flutuante são essenciais para representar valores reais com casas decimais em C. Diferente dos inteiros, eles seguem o padrão IEEE 754 e possuem limitações de precisão que podem causar erros sutis. Nesta aula, exploraremos os tipos float e double, como escrever constantes em notação científica, e as armadilhas ao comparar números de ponto flutuante.
Entender essas nuances é fundamental para escrever programas confiáveis, especialmente em áreas como simulações numéricas, processamento de sinais e finanças.
float vs double
Em C, os principais tipos de ponto flutuante são float (precisão simples) e double (precisão dupla). O tipo float ocupa 4 bytes (32 bits) e oferece cerca de 7 dígitos decimais de precisão. Já o double ocupa 8 bytes (64 bits) e oferece cerca de 15 dígitos decimais. Por padrão, constantes com ponto decimal (como 3.14) são do tipo double; para forçar float, use o sufixo f (ex.: 3.14f).
A escolha entre float e double depende da necessidade de precisão e do uso de memória. Em aplicações gráficas ou embarcadas com recursos limitados, float pode ser suficiente. Em cálculos científicos ou financeiros, double é preferível para evitar erros de arredondamento.
#include <stdio.h>
int main() {
float f = 3.141592653589793f;
double d = 3.141592653589793;
printf("float: %.15f\n", f);
printf("double: %.15f\n", d);
return 0;
}Precisão e armadilhas
A precisão limitada dos números de ponto flutuante pode levar a erros de arredondamento. Por exemplo, o valor 0.1 não pode ser representado exatamente em binário, resultando em pequenos erros. Esses erros podem se acumular em loops ou comparações.
Outra armadilha é a perda de precisão ao subtrair números muito próximos (cancelação catastrófica). Para evitar problemas, use double sempre que possível, evite subtrações de números quase iguais e nunca compare igualdade exata com ==.
#include <stdio.h>
int main() {
float a = 0.1f;
float b = 0.2f;
float c = 0.3f;
if (a + b == c) {
printf("Igual\n");
} else {
printf("Diferente: %.20f\n", a + b - c);
}
return 0;
}Notação científica
Constantes de ponto flutuante podem ser escritas em notação científica usando e ou E para o expoente. Por exemplo, 1.23e-4 equivale a 0.000123. O expoente deve ser inteiro, e a base é 10.
Essa notação é útil para representar números muito grandes ou muito pequenos de forma legível. Para float, adicione o sufixo f (ex.: 1.23e-4f).
#include <stdio.h>
int main() {
double grande = 6.022e23; // Número de Avogadro
float pequeno = 1.6e-19f; // Carga do elétron
printf("Grande: %e\n", grande);
printf("Pequeno: %e\n", pequeno);
return 0;
}Comparações
Comparar números de ponto flutuante com == é arriscado devido a erros de arredondamento. A prática recomendada é usar uma tolerância (epsilon) que define uma margem de erro aceitável. O epsilon típico para float é 1e-6 e para double 1e-9, mas pode variar conforme a aplicação.
A função fabs da biblioteca <math.h> retorna o valor absoluto de um double. Para float, use fabsf. Exemplo: if (fabs(a - b) < 1e-9).
#include <stdio.h>
#include <math.h>
int main() {
double a = 0.1 * 3;
double b = 0.3;
double epsilon = 1e-9;
if (fabs(a - b) < epsilon) {
printf("a e b são iguais dentro da tolerância\n");
} else {
printf("a e b são diferentes\n");
}
return 0;
}Boas práticas
- Prefira
doubleafloatem cálculos que exigem precisão. - Use sufixos
fpara constantesfloat. - Evite comparar igualdade exata; use tolerância.
- Cuidado com acumulação de erros em loops.
- Utilize
%epara exibir notação científica.
Referências
- cppreference: Floating constant
- cppreference: Limits of floating-point types
- What Every Computer Scientist Should Know About Floating-Point Arithmetic
- IBM: Floating-point comparisons
- IEEE 754 standard
Exercícios
- Escreva um programa que declare uma variável
floatcom valor 0.1 e umadoublecom valor 0.1, e imprima ambos com 20 casas decimais. Explique a diferença.✓ Resposta:
O float mostra imprecisão a partir da 7ª casa decimal, enquanto o double mantém precisão por mais dígitos.#include <stdio.h> int main() { float f = 0.1f; double d = 0.1; printf("float: %.20f\n", f); printf("double: %.20f\n", d); return 0; } - Calcule o valor de
0.1 + 0.2usandofloate compare com 0.3 usando uma tolerância de 1e-6. Imprima se são iguais ou diferentes.✓ Resposta:
Com tolerância, eles são considerados iguais.#include <stdio.h> #include <math.h> int main() { float a = 0.1f + 0.2f; float b = 0.3f; float eps = 1e-6f; if (fabsf(a - b) < eps) printf("Iguais\n"); else printf("Diferentes\n"); return 0; } - Escreva uma constante em notação científica para representar a velocidade da luz (3e8 m/s) como
doublee imprima-a em notação científica e decimal.✓ Resposta:#include <stdio.h> int main() { double c = 3e8; printf("Notação científica: %e\n", c); printf("Decimal: %f\n", c); return 0; } - Crie uma função
float_equalsque recebe doisfloate um epsilon, e retorna 1 se forem iguais dentro da tolerância, 0 caso contrário.✓ Resposta:#include <math.h> int float_equals(float a, float b, float eps) { return fabsf(a - b) < eps; } - Explique por que o código a seguir pode imprimir "Diferente" e corrija-o usando uma comparação segura.
#include <stdio.h> int main() { double a = 0.1 * 3; double b = 0.3; if (a == b) printf("Igual\n"); else printf("Diferente\n"); return 0; }✓ Resposta:O erro de arredondamento faz com queaseja ligeiramente diferente deb. Correção: use tolerância.#include <stdio.h> #include <math.h> int main() { double a = 0.1 * 3; double b = 0.3; double eps = 1e-9; if (fabs(a - b) < eps) printf("Igual\n"); else printf("Diferente\n"); return 0; }