Transforme matemática em código funcional. Implemente os algoritmos que você vai encontrar com mais frequência em computação científica e de engenharia.
Estatística (Média, Variância, Desvio Padrão)
Calcular estatísticas básicas de um conjunto de dados — de longe a operação mais comum em relatórios científicos e de engenharia.
iter 1: x = 1.500000000000000 err = 5.00e-01
iter 2: x = 1.416666666666667 err = 8.33e-02
iter 3: x = 1.414215686274510 err = 2.45e-03
iter 4: x = 1.414213562374690 err = 2.12e-06
iter 5: x = 1.414213562373095 err = 1.59e-12
sqrt(2) = 1.414213562373095
15 dígitos de precisão em apenas 5 iterações. O método de Newton converge quadraticamente, dobrando aproximadamente o número de dígitos corretos a cada passo.
Ressalva: um chute inicial ruim pode não convergir. Cuidado com pontos onde f'(x) = 0.
Método de Monte Carlo (Estimando Pi)
Uma técnica de simulação que usa amostragem aleatória para responder uma pergunta de forma probabilística. Jogue pontos aleatórios num quadrado 1×1 e estime pi pela fração que cai dentro do círculo unitário.
pi ≈ 4 × (pontos dentro do círculo / total de pontos)
#include<stdio.h>#include<stdlib.h>#include<time.h>intmain(void) {
int N;
printf("Tentativas: "); scanf("%d", &N);
srand((unsigned)time(NULL));
int inside = 0;
for (int i = 0; i < N; i++) {
double x = (double)rand() / RAND_MAX; // aleatório em [0,1]double y = (double)rand() / RAND_MAX;
if (x*x + y*y <= 1.0) inside++; // dentro do círculo?
}
double pi = 4.0 * inside / N;
printf("pi ≈ %.6f (N=%d)\n", pi, N);
printf("real: 3.141593\n");
return0;
}
Precisão vs. número de tentativas:
Tentativas N
Estimativa (ex.)
Erro
1.000
3,156
~0,01
10.000
3,1432
~0,001
100.000
3,14052
~0,001
1.000.000
3,141284
~0,0003
10.000.000
3,141552
~0,00004
A convergência é lenta (o erro encolhe como 1/√N), mas o método se estende facilmente a problemas em alta dimensão e é simples de programar. É muito usado em simulações de física e em engenharia financeira.
Integração Numérica (Regra do Trapézio & de Simpson)
Quando uma função não pode ser integrada analiticamente, você ainda consegue aproximar a integral numericamente.
#include<stdio.h>#include<math.h>doublef(double x) { returnsin(x); } // integrando// Regra do trapéziodoubletrapezoid(double a, double b, int n) {
double h = (b - a) / n;
double sum = (f(a) + f(b)) / 2.0;
for (int i = 1; i < n; i++)
sum += f(a + i * h);
return h * sum;
}
// Regra de Simpson (maior precisão)doublesimpson(double a, double b, int n) {
double h = (b - a) / n;
double sum = f(a) + f(b);
for (int i = 1; i < n; i++)
sum += (i % 2 == 0) ? 2*f(a+i*h) : 4*f(a+i*h);
return h / 3.0 * sum;
}
intmain(void) {
printf("Valor real da integral de sin(x) de 0 a pi = 2.0\n\n");
int ns[] = {10, 100, 1000};
for (int i = 0; i < 3; i++) {
printf("n=%4d trap=%.10f Simpson=%.10f\n",
ns[i], trapezoid(0, M_PI, ns[i]), simpson(0, M_PI, ns[i]));
}
return0;
}
Comparação de precisão: a regra de Simpson (precisão de quarta ordem) é ordens de magnitude mais precisa que a do trapézio.
Sistemas Lineares (Eliminação Gaussiana)
Resolva um sistema Ax = b usando eliminação direta seguida de substituição reversa.
2x + y - z = 8 -3x - y + 2z = -11 -2x + y + 2z = -3
#include<stdio.h>#include<math.h>#define N 3voidgauss(double a[N][N+1], double x[N]) {
// eliminação diretafor (int i = 0; i < N; i++) {
for (int j = i+1; j < N; j++) {
double ratio = a[j][i] / a[i][i];
for (int k = i; k <= N; k++)
a[j][k] -= ratio * a[i][k];
}
}
// substituição reversafor (int i = N-1; i >= 0; i--) {
x[i] = a[i][N];
for (int j = i+1; j < N; j++)
x[i] -= a[i][j] * x[j];
x[i] /= a[i][i];
}
}
intmain(void) {
double a[N][N+1] = {
{ 2, 1, -1, 8},
{-3, -1, 2, -11},
{-2, 1, 2, -3}
};
double x[N];
gauss(a, x);
for (int i = 0; i < N; i++)
printf("x[%d] = %.4f\n", i, x[i]);
return0; // resposta: x=2, y=3, z=-1
}
Método da Bisseção (Delimitando Raízes)
Se f(a) e f(b) têm sinais opostos, existe uma raiz entre eles. Divida o intervalo pela metade repetidamente. É mais lento que o método de Newton, mas com convergência garantida.
#include<stdio.h>#include<math.h>doublef(double x) { return x*x*x - x - 1; } // x^3 - x - 1 = 0doublebisection(double a, double b, double eps) {
while (b - a > eps) {
double mid = (a + b) / 2.0;
if (f(a) * f(mid) <= 0)
b = mid; // raiz na metade esquerdaelse
a = mid; // raiz na metade direita
}
return (a + b) / 2.0;
}
intmain(void) {
double root = bisection(1.0, 2.0, 1e-10);
printf("raiz de x^3 - x - 1 = 0: %.10f\n", root);
printf("checagem: f(raiz) = %e\n", f(root));
return0;
}
Bisseção: lenta (convergência linear), mas segura — sem precisar se preocupar com chute inicial.
Método de Newton: rápido (convergência quadrática), mas exige a derivada e um bom chute inicial.
Equações da forma dy/dx = f(x, y) podem ser resolvidas passo a passo a partir de um valor inicial. Movimento de projétil, modelos epidêmicos, circuitos elétricos, cinética química — todos têm esse formato.
Problema teste
dy/dx = -y
y(0) = 1
Exato: y(x) = e-x
Integramos até x=5 com três métodos e comparamos. O valor exato é e-5 ≈ 0,00673795.
① Euler method (1ª ordem)
O esquema mais simples — avança usando a inclinação no ponto atual. yn+1 = yn + h · f(xn, yn)
② Euler melhorado / Heun (2ª ordem)
Primeiro prediz, depois corrige usando a inclinação no ponto predito. k1 = f(xn, yn) k2 = f(xn+h, yn+h·k1) yn+1 = yn + h · (k1 + k2) / 2
③ Runge-Kutta 4 (RK4, 4ª ordem)
Avalia a inclinação quatro vezes (início, dois pontos médios e fim) e tira uma média ponderada. O solver de EDO de uso geral mais utilizado.
k1 = f(xn, yn)
k2 = f(xn + h/2, yn + h/2 · k1)
k3 = f(xn + h/2, yn + h/2 · k2)
k4 = f(xn + h, yn + h · k3)
yn+1 = yn + h/6 · (k1 + 2·k2 + 2·k3 + k4)
Programa de comparação
#include<stdio.h>#include<math.h>// dy/dx = f(x, y)doublef(double x, double y) {
(void)x;
return -y;
}
doubleeuler(double x0, double y0, double x_end, double h) {
double x = x0, y = y0;
while (x < x_end) {
y = y + h * f(x, y);
x = x + h;
}
return y;
}
doubleheun(double x0, double y0, double x_end, double h) {
double x = x0, y = y0;
while (x < x_end) {
double k1 = f(x, y);
double k2 = f(x + h, y + h * k1);
y = y + h * (k1 + k2) / 2;
x = x + h;
}
return y;
}
doublerk4(double x0, double y0, double x_end, double h) {
double x = x0, y = y0;
while (x < x_end) {
double k1 = f(x, y);
double k2 = f(x + h/2, y + h/2 * k1);
double k3 = f(x + h/2, y + h/2 * k2);
double k4 = f(x + h, y + h * k3);
y = y + h / 6 * (k1 + 2*k2 + 2*k3 + k4);
x = x + h;
}
return y;
}
intmain(void) {
double exact = exp(-5.0);
printf("exato y(5) = %.8f\n\n", exact);
printf(" h | Euler | Heun | RK4\n");
printf("--------+-------------+-------------+-------------\n");
double hs[] = {0.5, 0.1, 0.01};
for (int i = 0; i < 3; i++) {
double h = hs[i];
printf(" %5.3f | %.8f | %.8f | %.8f\n",
h, euler(0, 1, 5, h),
heun(0, 1, 5, h),
rk4(0, 1, 5, h));
}
return0;
}
Ordem de precisão: dividir h por 10 reduz o erro em cerca de 10× (Euler, 1ª ordem), 100× (Heun, 2ª) e 10000× (RK4, 4ª). RK4 já bate 6 dígitos com h=0,1, enquanto Euler mal chega a 2 dígitos mesmo com h=0,01.
Extensão para sistemas (oscilador de mola)
Movimento de projétil, Lotka-Volterra e o modelo SIR envolvem várias variáveis acopladas, e o RK4 se estende naturalmente. Aqui resolvemos um oscilador de mola m·x'' = -k·x reescrevendo como duas equações de primeira ordem: dx/dt = v, dv/dt = -(k/m)·x
Verificação de energia: em teoria E = ½·k·x² + ½·m·v² deve ser constante. O Euler simples desvia e a energia cresce; o RK4 a mantém essencialmente constante mesmo em intervalos longos de tempo. É uma das razões pelas quais o RK4 é tão usado.
Veja também
Os exemplos de projétil, SIR e Lotka-Volterra na página Construa Simulações ficam muito mais precisos quando você troca a atualização de Euler pelo RK4 — geralmente com menos passos.
Problemas de Desafio
Coloque o que aprendeu para funcionar nos problemas a seguir.
C1. Raiz cúbica pelo método de Newton
Ache x tal que x³ = a. Aplique o método de Newton com f(x) = x³ - a e f'(x) = 3x². Verifique que a=27 dá x=3.
C2. Integral definida por Monte Carlo
Calcule ∫₀¹ e-x² dx com Monte Carlo. (Faz parte da integral gaussiana — não tem forma fechada.) Dica: para x aleatório em [0,1], calcule f(x) = e-x². A média desses valores aproxima a integral.
C3. Distribuição de frequência & histograma
Gere 100 números aleatórios em [0,1], construa uma distribuição de frequência com bins de largura 0,1 e imprima um histograma em texto.
C4. Intervalo de confiança
Estime pi via Monte Carlo N vezes, depois calcule um intervalo de confiança de 95% (x̄ ± 2σ/√N).
C5. Crivo de primos (Crivo de Eratóstenes)
Enumere todos os primos ≤ N com o Crivo de Eratóstenes. Use um array e confirme a complexidade O(N log log N).
C6. Multiplicação de matrizes como função
Escreva uma função que calcula C = AB para matrizes A e B de n×n. Verifique o resultado em matrizes 3×3.
Q1. Qual a principal razão de erros de ponto flutuante ocorrerem em cálculos com double?
O binário só consegue representar valores com um número finito de dígitos
A CPU não é rápida o suficiente
O compilador tem bugs
Valores como 0,1 são dízimas em binário, então são armazenados como aproximações e introduzem erro de arredondamento.
Q2. Qual é a ideia central do método de Newton?
Aproximar a solução iterativamente usando retas tangentes da função
Buscar o valor com uma árvore binária
Estimar a solução por tentativas aleatórias
O ponto em que a reta tangente à estimativa atual cruza o eixo x vira a próxima estimativa, convergindo rapidamente para a solução.
Q3. Qual algoritmo é comumente usado para somar elementos de array com alta precisão?
Soma de Kahan
Quicksort
Algoritmo de Dijkstra
A soma de Kahan é uma técnica clássica que rastreia os bits de baixa ordem perdidos na adição de ponto flutuante e os reincorpora como termo de compensação.