Il y a quelque temps, j'avais écrit un code qui tentait de calculer sans utiliser les fonctions de bibliothèque. Hier, je passais en revue l'ancien code et j'ai essayé de le rendre aussi rapide que possible (et correct). Voici ma tentative jusqu'à présent:
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;
}
Ici, j'essaie de trouver pour que soit un peu plus de n, puis j'ajoute la valeur de logarithme de , qui est inférieure à 1. À ce stade, l'extension Taylor de peut être utilisée sans souci.
J'ai récemment développé un intérêt pour l'analyse numérique, et c'est pourquoi je ne peux m'empêcher de poser la question, à quelle vitesse ce segment de code peut-il être exécuté dans la pratique, tout en étant suffisamment correct? Dois-je passer à d'autres méthodes, par exemple, en utilisant la fraction continue, comme celle-ci ?
La fonction fournie avec la bibliothèque standard C est presque 5,1 fois plus rapide que cette implémentation.
MISE À JOUR 1 : En utilisant la série arctan hyperbolique mentionnée dans Wikipedia , le calcul semble être presque 2,2 fois plus lent que la fonction de journal de bibliothèque standard C. Cependant, je n'ai pas vérifié de manière approfondie les performances, et pour un plus grand nombre, mon implémentation actuelle semble être VRAIMENT lente. Je veux vérifier à la fois ma mise en œuvre pour les erreurs liées et le temps moyen pour une large gamme de nombres si je peux gérer Voici mon deuxième effort.
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;
}
Toute suggestion ou critique est appréciée.
MISE À JOUR 2: Sur la base des suggestions ci-dessous, j'ai ajouté ici des modifications incrémentielles, qui sont environ 2,5 fois plus lentes que l'implémentation de bibliothèque standard. Cependant, je ne l'ai testé que pour les entiers cette fois, pour des nombres plus importants, le temps d'exécution augmenterait. Pour l'instant. Je ne connais pas encore de techniques pour générer des nombres doubles aléatoires , donc ce n'est pas encore complètement référencé. Pour rendre le code plus robuste, j'ai ajouté des corrections pour les cas d'angle. L'erreur moyenne pour les tests que j'ai faits est d'environ . ≤ 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;
}