[英]Why my natural log function is so imprecise?
首先,我使用的是自然對數的近似值。 或查看此處(4.1.27) 以獲得更好的公式表示。
這是我的實現:
constexpr double eps = 1e-12;
constexpr double my_exp(const double& power)
{
double numerator = 1;
ull denominator = 1;
size_t count = 1;
double term = numerator / denominator;
double sum = 0;
while (count < 20)
{
sum += term;
numerator *= power;
#ifdef _DEBUG
if (denominator > std::numeric_limits<ull>::max() / count)
throw std::overflow_error("Denominator has overflown at count " + std::to_string(count));
#endif // _DEBUG
denominator *= count++;
term = numerator / denominator;
}
return sum;
}
constexpr double E = my_exp(1);
constexpr double my_log(const double& num)
{
if (num < 1)
return my_log(num * E) - 1;
else if (num > E)
return my_log(num / E) + 1;
else
{
double s = 0;
size_t tmp_odd = 1;
double tmp = (num - 1) / (num + 1);
double mul = tmp * tmp;
while (tmp >= eps)
{
s += tmp;
tmp_odd += 2;
tmp *= mul / tmp_odd;
}
return 2 * s;
}
}
您可能會明白我為什么要實現這些功能。 基本上,我想實現一個 pow function。 但是我的方法仍然給出了非常不精確的答案,例如 my_log(10) = 2.30256,但根據 google (ln 10 ~ 2.30259)。
my_exp() 非常精確,因為它的泰勒展開是高度收斂的。 根據谷歌,my_exp(1) = 2.718281828459,同時 e^1 = 2.71828182846。 但不幸的是,自然對數的情況並非如此,我什至不知道自然對數的這個系列是如何派生的(我的意思是來自上面的鏈接)。 而且我找不到關於這個系列的任何來源。
精度誤差從何而來?
if (num < 1) return my_log(num * E) - 1;
乘法有不精確性。 乘以 2 更准確。 當然, my_log(num) = my_log(2*num) - ln(2)
所以你需要更改1.0
常量。
是的,現在您將在-ln(2)
中出現舍入錯誤,而不是在*E
中出現舍入錯誤。 這通常不那么糟糕。
此外,您可以通過首先檢查 if (num<1/16) 然后使用my_log(num) = my_log(16*num) - ln(16)
來保存重復的舍入錯誤。 這只是一個舍入誤差。
至於你的核心循環中的錯誤,我懷疑罪魁禍首是s += tmp;
. 這是重復添加。 您可以在那里使用 Kahan 求和。
行tmp *= mul / tmp_odd;
表示每個術語也被所有先前術語的分母除以,即1, 1*3, 1*3*5, 1*3*5*7, ...
而不是1, 3, 5, 7, ...
正如公式所述。
因此,分子和分母應獨立計算:
double sum = 0;
double value = (num - 1) / (num + 1);
double mul = value * value;
size_t denom = 1;
double power = value;
double term = value;
while (term > eps)
{
sum += term;
power *= mul;
denom += 2;
term = power / denom;
}
return 2 * sum;
...
// Output for num = 1.5, eps = 1e-12
My func: 0.405465108108004513
Cmath log: 0.405465108108164385
------------
好多了!
將 epsilon 減少到1e-18
,我們達到了朴素求和的准確度限制:
// Output for num = 1.5, eps = 1e-18
My func: 0.40546510810816444
Cmath log: 0.405465108108164385
---------------
double sum = 0;
double error = 0;
double value = (num - 1) / (num + 1);
double mul = value * value;
size_t denom = 1;
double power = value;
double term = value;
while (term > eps)
{
double temp = sum + term;
if (abs(sum) >= abs(term))
error += (sum - temp) + term;
else
error += (term - temp) + sum;
sum = temp;
power *= mul;
denom += 2;
term = power / denom;
}
return 2 * (sum + error);
...
// Output for num = 1.5, eps = 1e-18
My func: 0.405465108108164385
Cmath log: 0.405465108108164385
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.