Qualche tempo fa avevo scritto un codice che tentava di calcolare il senza usare le funzioni di libreria. Ieri stavo rivedendo il vecchio codice e ho cercato di renderlo il più veloce possibile (e corretto). Ecco il mio tentativo finora:
const double ee = exp(1);
double series_ln_taylor(double n){ /* n = e^a * b, where a is an non-negative integer */
double lgVal = 0, term, now;
int i, flag = 1;
if ( n <= 0 ) return 1e-300;
if ( n * ee < 1 )
n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */
for ( term = 1; term < n ; term *= ee, lgVal++ );
n /= term;
/* log(1 - x) = -x - x**2/2 - x**3/3... */
n = 1 - n;
now = term = n;
for ( i = 1 ; ; ){
lgVal -= now;
term *= n;
now = term / ++i;
if ( now < 1e-17 ) break;
}
if ( flag == -1 ) lgVal = -lgVal;
return lgVal;
}
Qui sto cercando di trovare modo che sia appena sopra n, quindi aggiungo il valore del logaritmo di , che è inferiore a 1. A questo punto, l'espansione di Taylor del può essere utilizzata senza preoccuparsi.e a n log(1-x)
Di recente ho sviluppato un interesse per l'analisi numerica, ed è per questo che non posso fare a meno di porre la domanda, quanto più velocemente questo segmento di codice può essere eseguito nella pratica, pur essendo abbastanza corretto? Devo passare ad altri metodi, ad esempio utilizzando la frazione continua, come questa ?
La funzione fornita con la libreria standard C è quasi 5,1 volte più veloce di questa implementazione.
AGGIORNAMENTO 1 : Utilizzando la serie di arcani iperbolici menzionata in Wikipedia , il calcolo sembra essere quasi 2,2 volte più lento della funzione di registro della libreria C standard. Tuttavia, non ho verificato ampiamente le prestazioni e, per numeri più grandi, la mia attuale implementazione sembra REALMENTE lenta. Voglio controllare sia la mia implementazione per il limite di errori sia il tempo medio per un ampio intervallo di numeri, se posso farcela. Ecco il mio secondo sforzo.
double series_ln_arctanh(double n){ /* n = e^a * b, where a is an non-negative integer */
double lgVal = 0, term, now, sm;
int i, flag = 1;
if ( n <= 0 ) return 1e-300;
if ( n * ee < 1 ) n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */
for ( term = 1; term < n ; term *= ee, lgVal++ );
n /= term;
/* log(x) = 2 arctanh((x-1)/(x+1)) */
n = (1 - n)/(n + 1);
now = term = n;
n *= n;
sm = 0;
for ( i = 3 ; ; i += 2 ){
sm += now;
term *= n;
now = term / i;
if ( now < 1e-17 ) break;
}
lgVal -= 2*sm;
if ( flag == -1 ) lgVal = -lgVal;
return lgVal;
}
Ogni suggerimento o critica è apprezzato.
AGGIORNAMENTO 2: Sulla base dei suggerimenti forniti di seguito, ho aggiunto alcune modifiche incrementali qui, che sono circa 2,5 volte più lente dell'implementazione della libreria standard. Tuttavia, questa volta l'ho testato solo per interi , per numeri più grandi il tempo di esecuzione aumenterebbe. Per adesso. Non conosco ancora le tecniche per generare doppi numeri casuali , quindi non è ancora completamente testato. Per rendere il codice più solido, ho aggiunto correzioni per casi angolari. L'errore medio per i test che ho fatto è di circa . ≤ 1 e 308 4 e - 15
double series_ln_better(double n){ /* n = e^a * b, where a is an non-negative integer */
double lgVal = 0, term, now, sm;
int i, flag = 1;
if ( n == 0 ) return -1./0.; /* -inf */
if ( n < 0 ) return 0./0.; /* NaN*/
if ( n < 1 ) n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */
/* the cutoff iteration is 650, as over e**650, term multiplication would
overflow. For larger numbers, the loop dominates the arctanh approximation
loop (with having 13-15 iterations on average for tested numbers so far */
for ( term = 1; term < n && lgVal < 650 ; term *= ee, lgVal++ );
if ( lgVal == 650 ){
n /= term;
for ( term = 1 ; term < n ; term *= ee, lgVal++ );
}
n /= term;
/* log(x) = 2 arctanh((x-1)/(x+1)) */
n = (1 - n)/(n + 1);
now = term = n;
n *= n;
sm = 0;
/* limiting the iteration for worst case scenario, maximum 24 iteration */
for ( i = 3 ; i < 50 ; i += 2 ){
sm += now;
term *= n;
now = term / i;
if ( now < 1e-17 ) break;
}
lgVal -= 2*sm;
if ( flag == -1 ) lgVal = -lgVal;
return lgVal;
}