Há algum tempo eu escrevi um código que tentava calcular o sem usar as funções da biblioteca. Ontem, eu estava revisando o código antigo e tentei torná-lo o mais rápido possível (e correto). Aqui está minha tentativa até agora:
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;
}
Aqui, estou tentando encontrar para que acabe de n e, em seguida, adiciono o valor do logaritmo de , que é menor que 1. Neste ponto, a expansão do de Taylor pode ser usada sem se preocupar.n log(1-x)
Recentemente, interessei-me pela análise numérica, e é por isso que não consigo deixar de fazer a pergunta: quanto mais rápido esse segmento de código pode ser executado na prática, enquanto está correto o suficiente? Preciso mudar para outros métodos, por exemplo, usando a fração continuada, como esta ?
A função fornecida com a biblioteca padrão C é quase 5,1 vezes mais rápida que esta implementação.
ATUALIZAÇÃO 1 : Usando a série arctan hiperbólica mencionada na Wikipedia , o cálculo parece quase 2,2 vezes mais lento que a função de log de biblioteca padrão C. No entanto, não verifiquei extensivamente o desempenho e, para números maiores, minha implementação atual parece REALMENTE lenta. Quero verificar minha implementação quanto ao limite de erros e ao tempo médio para obter uma ampla variedade de números, se eu puder gerenciar. Aqui está o meu segundo esforço.
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;
}
Qualquer sugestão ou crítica é apreciada.
ATUALIZAÇÃO 2: Com base nas sugestões feitas abaixo, adicionei algumas mudanças incrementais aqui, que são cerca de 2,5 vezes mais lentas que a implementação da biblioteca padrão. No entanto, eu testei apenas para números inteiros desta vez, para números maiores, o tempo de execução aumentaria. Por enquanto. Ainda não conheço técnicas para gerar números duplos aleatórios , por isso ainda não está totalmente aferido. Para tornar o código mais robusto, adicionei correções para casos de canto. O erro médio para os testes que fiz é de cerca de . ≤ 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;
}