Я не думаю, что это действительно имеет отношение к ошибке приближения с плавающей запятой, я думаю, что есть еще один важный источник ошибок.
Как вы сами говорите, ваш метод очень прост. Вы принимаете приближение серии taylor на x=0
до этой функции, а затем оцениваете ее на уровне x=-100
.
Насколько вы точно ожидали, что этот метод будет и почему?
На высоком уровне вы должны ожидать, что ваш метод будет точным в узкой области около x=0
. Теорема аппроксимации Тейлора говорит вам, что, например, если вы возьмете N
условия серии около x=0
, ваше приближение будет точно соответствовать O(|x|)^(N+1)
. Поэтому, если вы берете 200 терминов, вы должны быть точными, например, в пределах 10^(-60)
или около того в диапазоне [-0.5, 0.5]
. Но в x=100
теорема Тейлора дает вам довольно жуткую оценку.
Концептуально, вы знаете, что e^{-x}
стремится к нулю, так как x
переходит на минус бесконечность. Но ваша функция аппроксимации является многочленом фиксированной степени, а любой непостоянный многочлен асимптотически стремится либо к бесконечности, либо к минусовой бесконечности. Поэтому относительная ошибка должна быть неограниченной, если вы рассматриваете весь диапазон возможных значений x
.
Итак, я думаю, вы должны пересмотреть свой подход. Одна вещь, которую вы можете учесть, - использовать только метод серии Тейлора для значений x
, удовлетворяющих -0.5f <= x <= 0.5f
.И для любого x
, превышающего 0.5f
, вы пытаетесь делить x
на два и вызывать функцию рекурсивно, а затем возводить в квадрат результат. Или что-то вроде этого.
Для получения наилучших результатов вы, вероятно, должны использовать установленный метод.
Edit:
я решил написать код, чтобы увидеть, насколько хорошо на самом деле работает моя идея. Он, по-видимому, идеально сочетается с реализацией библиотеки C в диапазоне x = -10000
до x = 10000
, по крайней мере, до такого количества бит точности, как я показываю. :)
Обратите также внимание на то, что мой метод является точным даже для значений x
больше 100, где метод серии Тейлора фактически теряет точность и на положительном конце.
#include <cmath>
#include <iostream>
long double taylor_series(long double x)
{
long double sum = 1.0, last = 1.0;
for(int i = 1; i < 200; i++) {
last *= x/i; //multiply the last term in the series from the previous term by x/n
sum += last; //add this new last term to the sum
}
return sum;
}
long double hybrid(long double x)
{
long double temp;
if (-0.5 <= x && x <= 0.5) {
return taylor_series(x);
} else {
temp = hybrid(x/2);
return (temp * temp);
}
}
long double true_value(long double x) {
return expl(x);
}
void output_samples(long double x) {
std::cout << "x = " << x << std::endl;
std::cout << "\ttaylor series = " << taylor_series(x) << std::endl;
std::cout << "\thybrid method = " << hybrid(x) << std::endl;
std::cout << "\tlibrary = " << true_value(x) << std::endl;
}
int main() {
output_samples(-10000);
output_samples(-1000);
output_samples(-100);
output_samples(-10);
output_samples(-1);
output_samples(-0.1);
output_samples(0);
output_samples(0.1);
output_samples(1);
output_samples(10);
output_samples(100);
output_samples(1000);
output_samples(10000);
}
Выход:
$ ./main
x = -10000
taylor series = -2.48647e+423
hybrid method = 1.13548e-4343
library = 1.13548e-4343
x = -1000
taylor series = -2.11476e+224
hybrid method = 5.07596e-435
library = 5.07596e-435
x = -100
taylor series = -8.49406e+24
hybrid method = 3.72008e-44
library = 3.72008e-44
x = -10
taylor series = 4.53999e-05
hybrid method = 4.53999e-05
library = 4.53999e-05
x = -1
taylor series = 0.367879
hybrid method = 0.367879
library = 0.367879
x = -0.1
taylor series = 0.904837
hybrid method = 0.904837
library = 0.904837
x = 0
taylor series = 1
hybrid method = 1
library = 1
x = 0.1
taylor series = 1.10517
hybrid method = 1.10517
library = 1.10517
x = 1
taylor series = 2.71828
hybrid method = 2.71828
library = 2.71828
x = 10
taylor series = 22026.5
hybrid method = 22026.5
library = 22026.5
x = 100
taylor series = 2.68812e+43
hybrid method = 2.68812e+43
library = 2.68812e+43
x = 1000
taylor series = 3.16501e+224
hybrid method = 1.97007e+434
library = 1.97007e+434
x = 10000
taylor series = 2.58744e+423
hybrid method = 8.80682e+4342
library = 8.80682e+4342
Edit:
Ибо кто заинтересован:
Был некоторый вопрос, поднятый в комментариях о том, насколько значительным ошибки с плавающей точкой в оригинале программа. Мое первоначальное предположение заключалось в том, что они были незначительными - я сделал тест, чтобы убедиться, что это правда или нет. Оказывается, это неверно, и происходит значительная ошибка с плавающей запятой, но даже без ошибок с плавающей запятой есть значительные ошибки, которые вводятся только по серии Тейлора. Истинное значение ряда Тейлора при 200 членах в x=-100
, по-видимому, находится рядом с -10^{24}
, а не 10^{-44}
. Я проверил это с помощью boost::multiprecision::cpp_rational
, который является произвольным типом рационального значения, построенным на их произвольном типе точности точности.
Выход:
x = -100
taylor series (double) = -8.49406e+24
(rational) = -18893676108550916857809762858135399218622904499152741157985438973568808515840901824148153378967545615159911801257288730703818783811465589393308637433853828075746484162303774416145637877964256819225743503057927703756503421797985867950089388433370741907279634245166982027749118060939789786116368342096247737/2232616279628214542925453719111453368125414939204152540389632950466163724817295723266374721466940218188641069650613086131881282494641669993119717482562506576264729344137595063634080983904636687834775755173984034571100264999493261311453647876869630211032375288916556801211263293563
= -8.46257e+24
library = 3.72008e-44
x = 100
taylor series (double) = 2.68812e+43
(rational) = 36451035284924577938246208798747009164319474757880246359883694555113407009453436064573518999387789077985197279221655719227002367495061633272603038249747260895707250896595889294145309676586627989388740458641362406969609459453916777341749316070359589697827702813520519796940239276744754778199440304584107317957027129587503199/1356006206645357299077422810994072904566969809700681604285727988319939931024001696953196916719184549697395496290863162742676361760549235149195411231740418104602504325580502523311497039304043141691060121240640609954226541318710631103275528465092597490136227936213123455950399178299
= 2.68812e+43
library = 2.68812e+43
Код:
#include <cmath>
#include <iostream>
#include <boost/multiprecision/cpp_int.hpp>
typedef unsigned int uint;
typedef boost::multiprecision::cpp_rational rational;
// Taylor series of exp
template <typename T>
T taylor_series(const T x) {
T sum = 1, last = 1;
for (uint i = 1; i < 200; i++) {
last = last * (x/i);
sum = sum + last;
}
return sum;
}
void sample(const int x) {
std::cout << "x = " << x << std::endl;
long double e1 = taylor_series(static_cast<long double>(x));
std::cout << "\ttaylor series (double) = " << e1 << std::endl;
rational e2 = taylor_series(static_cast<rational>(x));
std::cout << "\t (rational) = " << e2 << std::endl;
std::cout << "\t = " << static_cast<long double>(e2) << std::endl;
std::cout << "\tlibrary = " << expl(static_cast<long double>(x)) << std::endl;
}
int main() {
sample(-100);
sample(100);
}
Это зависит от того, что вы подразумеваете под «отлично» в «она отлично работает для положительного расчета ». В абсолютном выражении результат для положительной экспоненты на самом деле дальше от реального ответа, чем для отрицательной экспоненты! | 2.68811691354e + 43 - 2.68811714182e + 43 | = 2,2828e + 36 для e^100 по сравнению с | -8.42078025179e + 24 - 3.72007597602e-44 | = 8.42078e + 24 для e^-100 – WhiteViking
Ах да, извините, по-хорошему я имел в виду относительную ошибку, которая равна ~ 0.00000001 для положительной экспоненты и ~ 10^68 для отрицательного показателя :( – khfrekek
Это просто для интереса или у вас есть приложение, в котором вы можете использовать числа с двойной точностью, но не можете использовать стандартную библиотеку exp()? –