Calcolatore Numerico per Esercizi in C
Inserisci i parametri per calcolare soluzioni numeriche comuni negli esercizi di programmazione in C.
Guida Completa al Calcolo Numerico con Esercizi Svolti in C
Il calcolo numerico rappresenta una branca fondamentale della matematica applicata che si occupa di sviluppare algoritmi per approssimare soluzioni a problemi matematici complessi. In questo articolo esploreremo i principali metodi numerici implementabili in linguaggio C, con esempi pratici ed esercizi svolti.
1. Introduzione al Calcolo Numerico
Il calcolo numerico trova applicazione in numerosi campi scientifici e ingegneristici dove le soluzioni analitiche esatte non sono disponibili o sono troppo complesse da calcolare. I metodi numerici permettono di:
- Trovare radici di equazioni non lineari
- Risolvere sistemi di equazioni lineari
- Calcolare integrali definiti
- Risolvere equazioni differenziali ordinarie
- Approssimare funzioni con polinomi
2. Errori nel Calcolo Numerico
Prima di addentrarci nei metodi specifici, è cruciale comprendere i concetti di errore:
- Errore assoluto: |x* – x| dove x* è il valore approssimato e x il valore esatto
- Errore relativo: |x* – x|/|x| (se x ≠ 0)
- Errore di troncamento: derivante dall’interruzione di processi infiniti
- Errore di arrotondamento: causato dalla rappresentazione finita dei numeri
3. Metodi per la Ricerca delle Radici
3.1 Metodo di Bisezione
Il metodo di bisezione è uno dei più semplici metodi iterativi per trovare le radici di una funzione continua. Il principio si basa sul teorema degli zeri:
Se f è continua in [a,b] e f(a)·f(b) < 0, allora esiste almeno una radice in (a,b)
Algoritmo:
- Scegliere un intervallo [a,b] tale che f(a)·f(b) < 0
- Calcolare c = (a + b)/2
- Se f(c) = 0, c è la radice
- Altrimenti:
- Se f(a)·f(c) < 0, la radice è in [a,c]
- Se f(b)·f(c) < 0, la radice è in [c,b]
- Ripetere fino al raggiungimento della tolleranza desiderata
Implementazione in C:
#include <stdio.h>
#include <math.h>
#define TOLLERANZA 1e-6
#define MAX_ITER 100
double f(double x) {
return pow(x, 3) - 2*x - 5;
}
double bisezione(double a, double b) {
if (f(a) * f(b) >= 0) {
printf("Errore: f(a) e f(b) devono avere segni opposti\n");
return NAN;
}
double c;
int iter = 0;
while ((b - a) >= TOLLERANZA && iter < MAX_ITER) {
c = (a + b)/2;
if (f(c) == 0.0) break;
else if (f(c)*f(a) < 0) b = c;
else a = c;
iter++;
}
return c;
}
int main() {
double a = 2, b = 3;
double radice = bisezione(a, b);
printf("La radice approssimata è: %.6f\n", radice);
return 0;
}
3.2 Metodo di Newton-Raphson
Il metodo di Newton (o Newton-Raphson) è un metodo iterativo che converge più rapidamente della bisezione, ma richiede la conoscenza della derivata della funzione.
Formula iterativa: xₙ₊₁ = xₙ – f(xₙ)/f'(xₙ)
Implementazione in C:
#include <stdio.h>
#include <math.h>
#define TOLLERANZA 1e-6
#define MAX_ITER 100
double f(double x) {
return pow(x, 3) - 2*x - 5;
}
double df(double x) {
return 3*pow(x, 2) - 2;
}
double newton(double x0) {
double x = x0;
int iter = 0;
while (iter < MAX_ITER) {
double fx = f(x);
if (fabs(fx) < TOLLERANZA) break;
double dfx = df(x);
if (dfx == 0) {
printf("Errore: derivata nulla\n");
return NAN;
}
double x_new = x - fx/dfx;
if (fabs(x_new - x) < TOLLERANZA) break;
x = x_new;
iter++;
}
return x;
}
int main() {
double x0 = 2.0;
double radice = newton(x0);
printf("La radice approssimata è: %.6f\n", radice);
return 0;
}
4. Integrazione Numerica
4.1 Regola del Trapezio
La regola del trapezio è un metodo semplice per approssimare l’integrale definito di una funzione. L’idea è di approssimare l’area sotto la curva con una serie di trapezi.
Formula: ∫ₐᵇ f(x)dx ≈ (h/2)[f(a) + 2f(x₁) + 2f(x₂) + … + 2f(xₙ₋₁) + f(b)]
dove h = (b-a)/n e xᵢ = a + ih per i = 1,2,…,n-1
Implementazione in C:
#include <stdio.h>
#include <math.h>
double f(double x) {
return exp(-x*x); // Funzione esempio: e^(-x^2)
}
double trapezio(double a, double b, int n) {
double h = (b - a)/n;
double integrale = (f(a) + f(b))/2.0;
for (int i = 1; i < n; i++) {
double x = a + i*h;
integrale += f(x);
}
integrale *= h;
return integrale;
}
int main() {
double a = 0, b = 1;
int n = 1000;
double risultato = trapezio(a, b, n);
printf("Approssimazione dell'integrale: %.6f\n", risultato);
return 0;
}
4.2 Regola di Simpson
La regola di Simpson fornisce un’approssimazione generalmente più accurata rispetto alla regola del trapezio, utilizzando polinomi quadratici invece che lineari.
Formula: ∫ₐᵇ f(x)dx ≈ (h/3)[f(a) + 4f(x₁) + 2f(x₂) + 4f(x₃) + … + 2f(xₙ₋₂) + 4f(xₙ₋₁) + f(b)]
dove h = (b-a)/n e n deve essere pari
5. Risoluzione di Sistemi Lineari
5.1 Eliminazione di Gauss
L’eliminazione di Gauss è un metodo per risolvere sistemi di equazioni lineari trasformando la matrice dei coefficienti in una matrice triangolare superiore.
Algoritmo:
- Costruire la matrice aumentata [A|b]
- Per ogni colonna k da 1 a n-1:
- Trovare il pivot (elemento non nullo)
- Per ogni riga i sotto il pivot:
- Calcolare il moltiplicatore m = a[i][k]/a[k][k]
- Sottrarre m volte la riga del pivot dalla riga i
- Risolvere il sistema triangolare con sostituzione all’indietro
Implementazione in C:
#include <stdio.h>
#define N 3
void gauss(double a[N][N+1]) {
int i, j, k;
double m;
// Eliminazione
for (k = 0; k < N-1; k++) {
for (i = k+1; i < N; i++) {
m = a[i][k]/a[k][k];
for (j = k; j <= N; j++) {
a[i][j] -= m*a[k][j];
}
}
}
// Sostituzione all'indietro
double x[N];
x[N-1] = a[N-1][N]/a[N-1][N-1];
for (i = N-2; i >= 0; i--) {
double sum = 0;
for (j = i+1; j < N; j++) {
sum += a[i][j]*x[j];
}
x[i] = (a[i][N] - sum)/a[i][i];
}
// Stampa soluzione
printf("Soluzione:\n");
for (i = 0; i < N; i++) {
printf("x[%d] = %.4f\n", i, x[i]);
}
}
int main() {
double a[N][N+1] = {
{2, 1, -1, 8},
{-3, -1, 2, -11},
{-2, 1, 2, -3}
};
gauss(a);
return 0;
}
6. Confronto tra Metodi Numerici
| Metodo | Velocità di Convergenza | Requisiti | Vantaggi | Svantaggi | Costo Computazionale |
|---|---|---|---|---|---|
| Bisezione | Lineare (errore ~1/2ⁿ) | f continua, f(a)·f(b) < 0 | Sempre convergente | Lento | Basso |
| Newton-Raphson | Quadratica (errore ~e²) | f derivabile, f'(x) ≠ 0 | Molto veloce vicino alla soluzione | Può divergere, richiede derivata | Moderato |
| Secante | Superlineare (~1.618) | f continua | Non richiede derivata | Meno veloce di Newton | Moderato |
| Regola del Trapezio | O(h²) | f integrable | Semplice da implementare | Meno accurato di Simpson | Basso |
| Regola di Simpson | O(h⁴) | f integrable, n pari | Molto accurato | Richiede n pari | Moderato |
7. Ottimizzazione delle Prestazioni in C
Quando si implementano algoritmi numerici in C, è importante considerare alcuni aspetti per ottimizzare le prestazioni:
- Evita calcoli ridondanti: Memorizza valori che vengono riutilizzati
- Usa tipi di dati appropriati:
floatper precisione semplice (6-7 cifre decimali)doubleper precisione doppia (15-16 cifre decimali)long doubleper precisione estesa
- Ottimizza i loop:
- Minimizza le operazioni all’interno dei cicli
- Usa
restrictper pointer aliasing - Considera il loop unrolling per cicli piccoli
- Parallelizzazione:
- Usa OpenMP per parallelizzare loop indipendenti
- Considera l’uso di SIMD (SSE, AVX) per operazioni vettoriali
- Gestione degli errori:
- Controlla sempre i domini delle funzioni (es: log(x) per x ≤ 0)
- Usa
isnan()eisinf()per rilevare valori non validi
8. Librerie Utili per il Calcolo Numerico in C
Esistono numerose librerie che possono semplificare l’implementazione di algoritmi numerici in C:
| Libreria | Descrizione | Funzionalità Principali | Licenza |
|---|---|---|---|
| GSL | GNU Scientific Library | Radici, integrazione, algebra lineare, numeri casuali, FFT | GPL |
| LAPACK | Linear Algebra Package | Sistemi lineari, autovalori, decomposizioni matrici | BSD |
| FFTW | Fastest Fourier Transform in the West | Trasformate di Fourier discrete | GPL |
| BLAS | Basic Linear Algebra Subprograms | Operazioni vettoriali e matrici | Pubblico dominio |
| GMP | GNU Multiple Precision | Aritmetica a precisione arbitraria | LGPL |
9. Errori Comuni e Come Evitarli
Durante l’implementazione di algoritmi numerici in C, è facile incorrere in errori che possono compromettere l’accuratezza o la stabilità dei risultati:
- Cancellazione catastrofica:
Si verifica quando si sottraggono due numeri quasi uguali, perdendo precisione. Soluzione: riorganizzare le formule o usare precisione maggiore.
Esempio problematico: (1 – cos(x))/x per x piccolo
- Overflow/Underflow:
L’overflow si verifica quando un numero supera il massimo rappresentabile, mentre l’underflow quando è troppo piccolo. Soluzione: usare scale appropriate o logaritmi.
- Instabilità numerica:
Algoritmi apparentemente corretti possono essere numericamente instabili. Soluzione: analizzare la propagazione degli errori e usare algoritmi stabili.
- Convergenza a soluzioni errate:
Alcuni metodi iterativi possono convergere a soluzioni non desiderate. Soluzione: usare buoni valori iniziali e criteri di arresto robusti.
- Errori di arrotondamento accumulati:
In algoritmi iterativi, gli errori di arrotondamento possono accumularsi. Soluzione: usare precisione maggiore o algoritmi che minimizzano gli errori.
10. Applicazioni Pratiche del Calcolo Numerico
Il calcolo numerico trova applicazione in numerosi campi:
- Fisica computazionale:
- Simulazione di sistemi dinamici
- Meccanica quantistica
- Teoria dei campi
- Ingegneria:
- Analisi strutturale (metodo degli elementi finiti)
- Dinamica dei fluidi (CFD)
- Progettazione di circuiti elettronici
- Finanza computazionale:
- Valutazione di opzioni (modello Black-Scholes)
- Analisi del rischio
- Ottimizzazione di portafoglio
- Grafica computerizzata:
- Ray tracing
- Animazione fisicamente accurata
- Modellazione 3D
- Machine Learning:
- Ottimizzazione di funzioni di costo
- Reti neurali
- Elaborazione del segnale
11. Esercizi Pratici con Soluzioni
Esercizio 1: Metodo di Bisezione
Testo: Trovare la radice dell’equazione f(x) = x³ – x – 1 nell’intervallo [1, 2] con tolleranza 10⁻⁴.
Soluzione in C:
#include <stdio.h>
#include <math.h>
double f(double x) {
return pow(x, 3) - x - 1;
}
double bisezione(double a, double b, double tol) {
if (f(a) * f(b) >= 0) {
printf("Errore: f(a) e f(b) devono avere segni opposti\n");
return NAN;
}
double c;
while ((b - a) >= tol) {
c = (a + b)/2;
if (f(c) == 0.0) break;
else if (f(c)*f(a) < 0) b = c;
else a = c;
}
return c;
}
int main() {
double a = 1, b = 2, tol = 1e-4;
double radice = bisezione(a, b, tol);
printf("Radice approssimata: %.6f\n", radice);
printf("Valore funzione: %.6f\n", f(radice));
return 0;
}
Output atteso: Radice approssimata: 1.324718
Esercizio 2: Regola di Simpson
Testo: Calcolare l’integrale di f(x) = sin(x) da 0 a π/2 usando la regola di Simpson con n=10.
Soluzione in C:
#include <stdio.h>
#include <math.h>
double f(double x) {
return sin(x);
}
double simpson(double a, double b, int n) {
double h = (b - a)/n;
double integrale = f(a) + f(b);
for (int i = 1; i < n; i++) {
double x = a + i*h;
integrale += f(x) * ((i % 2 == 0) ? 2 : 4);
}
integrale *= h/3;
return integrale;
}
int main() {
double a = 0, b = M_PI/2;
int n = 10;
double risultato = simpson(a, b, n);
printf("Integrale approssimato: %.6f\n", risultato);
printf("Valore esatto: 1.000000\n");
return 0;
}
Esercizio 3: Eliminazione di Gauss
Testo: Risolvere il seguente sistema lineare usando l’eliminazione di Gauss:
2x + y – z = 8
-3x – y + 2z = -11
-2x + y + 2z = -3
Soluzione: Vedi implementazione nella sezione 5.1