141 lines
5.1 KiB
Markdown
141 lines
5.1 KiB
Markdown
|
# Calcolo numerico
|
||
|
|
||
|
## Compilare un programma
|
||
|
|
||
|
Per eseguire un programma, un computer fa questa cosa: legge le informazioni
|
||
|
dall'input e le mette nella memoria; poi da qui legge le istruzioni
|
||
|
sequenzialmente e se c'è qualche calcolo da fare, lo fa fare alla ALU
|
||
|
(aritmetic logic unit) e memorizza il risultato nella memoria; alla fine passa
|
||
|
tutto in output.
|
||
|
Per essere eseguibile, un programma deve essere scritto in codice macchina
|
||
|
e per questo si usano i linguaggi di programmazione che, attraverso il
|
||
|
compilatore, vengono tradotti in codice macchina:
|
||
|
|
||
|
- codice sorgente,
|
||
|
- compilatore,
|
||
|
- codice oggetto,
|
||
|
- linker (aggiunge le librerie),
|
||
|
- codice eseguibile.
|
||
|
|
||
|
## Errori di implementazione
|
||
|
|
||
|
Quando si passa dal modello astratto a quello implementato, si distinguono
|
||
|
tre tipi di errori:
|
||
|
|
||
|
- errori analitici: causati dalla necessità di una aritmetica discreta,
|
||
|
cioè il fatto che non si possa rappresentare una cosa continua e quindi
|
||
|
la si fa a punti;
|
||
|
- errori inerenti o algoritmici (o di round-off): causati dal numero finito di
|
||
|
cifre significative. Dipendono dall'algoritmo utilizzato, perché ci sono
|
||
|
metodi migliori di altri per evitare questo tipo di problemi.
|
||
|
|
||
|
## Rappresentazione di un numero
|
||
|
|
||
|
L'obiettivo è quello di tenere sotto controllo gli errori di round-off.
|
||
|
Per rappresentare un numero, esistono diverse rappresentazioni, tra cui quella
|
||
|
di Von Neumann: si dedicano $n$ cifre significative alla mantissa e poi un tot
|
||
|
anche alla caratteristica, cioè all'ordine di grandezza (che generalmente è in
|
||
|
base 2).
|
||
|
Con $n$ cifre significative in base 2, il numero più alto rappresentabile è
|
||
|
$2^n -1$ perché con due cifre (0 e 1), ci sono $2^n$ possibili numeri
|
||
|
rappresentabili e, se posti in ordine crescente, ognuno dista dal precedente
|
||
|
un'unità, con il minimo che vale 0 (tutti 0), per cui il numero più alto che si
|
||
|
può rappresentare è $2^n -1$:
|
||
|
|
||
|
- 0 è l'1,
|
||
|
- 1 e il 2,
|
||
|
- ...$2^n -1$ è il $2^n$-esimo.
|
||
|
|
||
|
questo numero corrisponde a un numero in base dieci di $m$ cifre significative,
|
||
|
per cui:
|
||
|
|
||
|
$$
|
||
|
m = \log(2^n -1)
|
||
|
$$
|
||
|
|
||
|
quindi per un numero con 24 bit per la mantissa, si hanno circa 7 cifre
|
||
|
significative in base 10.
|
||
|
In precisione semplice (floating point):
|
||
|
|
||
|
- un bit per il segno,
|
||
|
- 23 bit per la mantissa (quindi 7 cifre significative),
|
||
|
- 8 bits per la caratteristica.
|
||
|
|
||
|
In precisione doppia (duble) si raddoppiano i bite alla mantissa e le cifre
|
||
|
significative diventano 17, però il tempo per le moltiplicazioni è triplicato
|
||
|
e si occupa più memoria, quindi non è che convenga moltissimo.
|
||
|
|
||
|
Esiste anche la rappresentazione fixed-point, in cui c'è un bit per il segno
|
||
|
e i restanti per il numero in sé, in cui da qualche parte c'è la virgola.
|
||
|
Nel caso del fixed pointd, l'errore relativo può variare moltissimo a seconda
|
||
|
del numero rappresentato, mentre per i float è lo stesso per ogni numero: la
|
||
|
densità relativa non è costante.
|
||
|
Quando un insieme di rappresentazione non è chiuso rispetto ad un'operazione,
|
||
|
il risultato presenta sicuramente errore di round-off e deve essere
|
||
|
approssimato.
|
||
|
|
||
|
|
||
|
## Errori algoritmici
|
||
|
|
||
|
Per numeri troppo grandi o troppo piccoli che non si riesce a
|
||
|
rappresentare, si parla rispettivamente di overflow o underflow. Per questo
|
||
|
motivo, somme e soprattutto differenze di numeri grandi portano grandi errori
|
||
|
dovuti all'arrotondamento: bisogna cercare di evitarlo se possibile. Per
|
||
|
esempio, per calcolare un polinomio, conviene usare il metodi di
|
||
|
Ruffini-Horner perché riduce il numero di operazioni da effettuare:
|
||
|
|
||
|
- metodo semplice:
|
||
|
|
||
|
$$
|
||
|
p(x) = a_0 + a_1x + a_2x^2 + ... +a_nx^n
|
||
|
$$
|
||
|
|
||
|
che sono n addizioni e n(n+1)/2 moltiplicazioni, quindi $\sim n^2$.
|
||
|
- metodo di R-H:
|
||
|
|
||
|
$$
|
||
|
p (x) = (((((a_n)x +a_{n-1})x +a_{n-2}x +...
|
||
|
$$
|
||
|
|
||
|
che sono n addizioni e n moltiplicazioni, quindi $\sim n$.
|
||
|
|
||
|
Il problema si verifica anche se si sottraggono due numeri molto simili tra
|
||
|
loro, perché il risultato è un numero molto piccolo che potrebbe finire in
|
||
|
underflow (si parla di "cancellazione catastrofica").
|
||
|
|
||
|
\textcolor{orange}{oss:} Il costo computazionale della risoluzione di un
|
||
|
sistema lineare in $n$ equazioni è asintoticamente uguale al costo del
|
||
|
prodotto di due matrici $nxn$. Esistono algoritmi che non richiedono più di
|
||
|
$k \cdot n^{\alpha}$ operazioni, col il più piccolo valore noto di $\alpha$
|
||
|
pari a 0.2375.
|
||
|
|
||
|
Alcuni algoritmi sono definiti "instabili": accade quando gli errori di
|
||
|
round-off si accumulano fino a portare a risultati completamente errati.
|
||
|
Esistono problemi detti "mal condizionati" che con qualsiasi algoritmo danno
|
||
|
errori talmente elevati da rendere il risultato privo di significato. In
|
||
|
questi casi, piccole variazioni dei dati iniziali portano a grandi variazioni
|
||
|
nei risultati. Si chiama "numero di condizionamento del problema" il seguente
|
||
|
rapporto:
|
||
|
|
||
|
$$
|
||
|
\frac{\Delta r}{\Delta \alpha} =
|
||
|
\frac{\text{\% errore risultato}}{\text{\% errore dato iniziale}}
|
||
|
$$
|
||
|
|
||
|
che può quindi essere espresso in questo modo:
|
||
|
|
||
|
$$
|
||
|
\Delta \alpha = \frac{x + h - x}{x} = \frac{h}{x}
|
||
|
\hspace{100pt}
|
||
|
\Delta r = \frac{f(x + h)-f(x)}{f(x)}
|
||
|
$$
|
||
|
$$
|
||
|
\frac{\Delta r}{\Delta \alpha} =
|
||
|
\frac{f(x + h)-f(x)}{h} \cdot \frac{x}{f(x)} =
|
||
|
f'(x) \cdot \frac{x}{f(x)}
|
||
|
$$
|
||
|
|
||
|
Se questo numero è $<< 1$, allora il problema è poco sensibile ai dati
|
||
|
iniziali.
|
||
|
|