Я раніше писав код, який намагався обчислити не використовуючи бібліотечні функції. Вчора я переглядав старий код, і намагався зробити його якомога швидше (і правильно). Ось моя спроба поки що:
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;
}
Тут я намагаюся знайти так , що ті трохи більше п, а потім додати значення логарифм п , що менше 1. У цей момент розширення Тейлораlog(1-x)можна використовувати, не переживаючи.
Нещодавно у мене з'явився інтерес до чисельного аналізу, і тому я не можу не задати питання, наскільки швидше цей сегмент коду можна запустити на практиці, дотримуючись при цьому достатньо правильності? Чи потрібно переходити до якихось інших методів, наприклад, використовуючи продовження дробу, як це ?
Функція надається зі стандартною бібліотекою С, майже у 5,1 рази швидша, ніж ця реалізація.
ОНОВЛЕННЯ 1 : Використовуючи згаданий у Вікіпедії гіперболічний арктановий ряд , здається, що обчислення майже в 2,2 рази повільніше, ніж стандартна функція журналу бібліотеки С. Хоча я ще не детально перевіряв продуктивність, і для більшої кількості моє поточне впровадження здається НАДУСЬКО повільним. Я хочу перевірити як мою реалізацію на наявність помилок, так і середній час для широкого діапазону чисел, якщо я можу керувати. Ось моє друге зусилля.
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;
}
Будь-яка пропозиція чи критика цінується.
ОНОВЛЕННЯ 2: На підставі запропонованих нижче пропозицій я тут додав деякі додаткові зміни, що приблизно в 2,5 рази повільніше, ніж стандартна реалізація бібліотеки. Однак я перевірив це лише на цілі числа цього разу, для більшої кількості тривалість виконання збільшиться. Зараз. Я ще не знаю методики генерації випадкових подвійних чисел ≤ 1 e 308 , тому це ще не повністю орієнтоване. Щоб зробити код більш надійним, я додав виправлення для кутових справ. Середня помилка для зроблених нами тестів становить близько 4 е - 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;
}