Sì, c'è! A condizione che almeno uno di x
e a
è positivo, è possibile utilizzare:
f(x, a) = a/(sqrt(x + a) + sqrt(x))
che è perfettamente numericamente stabile, ma non vale la pena una funzione di libreria a sé stante. Naturalmente, quando x = a = 0
, il risultato dovrebbe essere 0
.
Spiegazione: sqrt(x + a) - sqrt(x)
è uguale a (sqrt(x + a) - sqrt(x)) * (sqrt(x + a) + sqrt(x))/(sqrt(x + a) + sqrt(x))
. Ora moltiplica i primi due termini per ottenere sqrt(x+a)^2 - sqrt(x)^2
, che si semplifica a a
.
Ecco un esempio che illustra la stabilità: il caso fastidioso per l'espressione originale è dove x + a
e x
sono molto vicine in termini di valore (o equivalentemente quando a
è molto più piccolo in grandezza rispetto x
). Ad esempio, se x = 1
e a
sono di piccole dimensioni, sappiamo da un'espansione di Taylor intorno a che sqrt(1 + a)
deve essere , quindi sqrt(1 + a) - sqrt(1)
dovrebbe essere vicino a a/2 - a^2/8
. Proviamo questo per una particolare scelta di piccoli a
. Ecco la funzione originale (scritto in Python, in questo caso, ma è possibile trattarlo come pseudocodice):
def f(x, a):
return sqrt(x + a) - sqrt(x)
ed ecco la versione stabile:
def g(x, a):
if a == 0:
return 0.0
else:
return a/((sqrt(x + a) + sqrt(x))
Ora vediamo cosa otteniamo con x = 1
e a = 2e-10
:
>>> a = 2e-10
>>> f(1, a)
1.000000082740371e-10
>>> g(1, a)
9.999999999500001e-11
Il valore che dovremmo abbiamo ottenuto è (fino a precisione della macchina): a/2 - a^2/8
- per questo particolare a
, i termini di ordine cubico e superiore sono insignificanti nel contesto dei galleggianti a precisione doppia IEEE 754, che forniscono solo circa 16 cifre decimali di precisione. Facciamo calcolare tale valore per il confronto:
>>> a/2 - a**2/8
9.999999999500001e-11
Alcune librerie, in particolare Boost, offrono una funzione 'sqrt1pm1()' progettato per calcolare sqrt (x + 1) -1 precisione. Se si utilizza già tale libreria, è possibile utilizzare tale funzione per implementare 'sqrt (x + a) -sqrt (x)' come 'sqrt1pm1 (a/x) * sqrt (x)' in modo numericamente robusto. – njuffa
@njuffa: Ah, molto interessante. Mentre funzioni come 'log1p' e' expm1' sono comuni, non ho mai incontrato 'sqrt1pm1' prima. Da un lato, sembra strano creare una funzione separata per questo quando è così facile da emulare. D'altra parte, avrei sicuramente trovato l'occasione di usarlo se fosse disponibile nella libreria standard C. –
@MarkDickinson Come ha mostrato Kahan, anche 'log1p' e' expm1' sono facili da emulare. Presumibilmente il punto di fornire tali funzioni in una libreria è quello di fornire le implementazioni più veloci e accurate ai programmatori che non sono particolarmente ben informati sull'analisi numerica. – njuffa