Ti propongo un fatto carino, che spero possa essere utile per farti comprendere quanto bisogna stare attenti talvolta all'aritmetica floating point.
Calcola $\log(1+x)$ per $x$ molto piccolo.
Per un $x> -1$ tale che $|x|< \mu$, dove $\mu$ è l'unita di roundoff, hai che necessariamente
1+x == 1
. Perciò $\log(1+x)=\log(1)=0$:
che è clamorosamente errato. Infatti, per $x = 10^(-18)$, hai che $\log(1+x) \approx 10^(-18)$ in aritmetica esatta, non $0$ come invece trovi sul tuo pc! L'errore
relativo infatti è molto grande $E_r(x)\approx\frac{10^(-18)-0}{10^(-18)} = 1$.
Come ovviare a questo inconveniente? Usando Taylor con resto di Lagrange in un intorno di $x$ abbiamo
$\log(1+x)=x-\frac{x^2}{2} + \mathcal{O}(x^3/3)$
Perciò l'errore commesso è dell'ordine di $\frac{x^3}{3}$. Pertanto, per valori $|x|<10^{-4}$ trovi che $\log(1+x)$ viene valutata con un errore $|E(x)| < 10^{-12}$. Questo breve listato in C++ ti permette di fare diversi test:
- Codice:
#include <iostream>
#include <cmath>
struct NonAccett{
std::string messaggio;
NonAccett(std::string s): messaggio{std::move(s)} {}
const char* what() const {return messaggio.c_str();}
};
double log1px(const double& x){
if (x<=-1) {
throw NonAccett(std::to_string(x) + " è minore di -1, non accettabile");
}
if (std::fabs(x)>1e-4) {
return log(1+x); //non ci sono grandi problemi di accuratezza
}
return x*(1 - 0.5*x);
}
int main(){
const double x{1e-15};
try {
double test{log1px(x)};
std::cout << test << std::endl;
return 0;
} catch (const NonAccett& tp) {
std::cout << tp.what() <<std::endl;
return 1;
}
}