【问题标题】:Euler's number with stop condition带停止条件的欧拉数
【发布时间】:2021-01-13 07:55:07
【问题描述】:

原始过时代码: Write an algorithm that compute the Euler's number until

算法课的教授给了我以下作业:

编写一个 C/C++ 程序,以 eps > 0 的给定精度计算欧拉数 (e) 的值。 提示:数字 e = 1 + 1/1! +1/2! + ... + 1 / n! + ... = 2.7172 ... 可以计算为序列 x_0, x_1, x_2, ... 的元素之和,其中 x_0 = 1, x_1 = 1+ 1/1 !, x_2 = 1 + 1/ 1! +1/2 !, ...,只要条件 |x_(i+1) - x_i|,求和就会继续>= eps 是有效的。

正如他进一步解释的那样,eps 是算法的精度。例如,精度可以是 1/100 |x_(i + 1) - x_i| = ( x_(i+1) - x_i ) 的绝对值

目前,我的程序如下所示:

#include<iostream>
#include<cstdlib>
#include<math.h>

// Euler's number

using namespace std;

double factorial(double n)
{
    double result = 1;
    for(double i = 1; i <= n; i++)
    {
        result = result*i;
    }
    return result;
}

int main()
{
    long double euler = 2;

    long double counter = 2;
    long double epsilon = 1.0/1000;
    long double moduloDifference;

    do
    {
        euler+=  1 / factorial(counter);
        counter++;
        moduloDifference = (euler + 1 / factorial(counter+1) - euler);
    } while(moduloDifference >= epsilon);
    printf("%.35Lf ", euler );
    return 0;
}

问题:

  1. 似乎我的 epsilon 值无法正常工作。它应该控制精度。例如,当我希望精度为 5 位时,我将其初始化为 1.0/10000,并在 8 后被截断之前输出 3 位(.7180)。
  2. 当我使用 long double 数据类型并且 epsilon = 1/10000 时,我的 epsilon 的值为 0,我的程序将无限运行。然而,如果将数据类型从 long double 更改为 double,它就可以工作。为什么使用 long double 数据类型时 epsilon 变为 0?
  3. 如何优化求欧拉数的算法?我知道,我可以摆脱该函数并即时计算欧拉值,但每次尝试这样做之后,我都会收到其他错误。

【问题讨论】:

  • 在将其与您的 epsilon 进行比较之前,您必须获取差异的绝对值
  • 1.为什么是阶乘?你知道你正在计算1, 1*2, 1*2*3, 1*2*3*4 ...。那么为什么不使用最后一个结果并将其乘以 i 呢? 2. 由于 Jerry Coffin 的回答建议添加大小值不起作用......他建议颠倒顺序,但如果不知道 n 提前或拥有“无限”内存,这是不可能的。相反,您需要像我在here look for [edit1] integration precision 中所做的那样将子结果分成大小不等
  • 此外,如果您不受方程约束,还有一种方法更适合使用 e = (1+1/x)^x 在二进制计算机上计算 e,请参阅 Euler number with tasks... 精度只是一个函数的bits 可以从log(precision)/log(2) 计算...但是这将不再需要差异测试,因此如果这是一项作业,则可能不会将其视为正确的作业...
  • @Secundi 是的,我会将.0 添加到所有浮动文字中,以确保不会发生整数/浮点数或舍入之间的转换......我在处理这些东西时有点偏执使用编译器(不时)可以1/2.5 截断为整数算术,然后再转换回浮点数,使事情变得不精确和缓慢......
  • @max,请在这里更新您的问题,因为您几乎重复的问题已经收到了一些有用的答案,尤其是在优化方面。也许应该将原始问题标记为重复。

标签: c++ algorithm factorial eulers-number


【解决方案1】:

以这种方式计算欧拉常数的一个问题非常简单:您从一些相当大的数字开始,但由于每个项中的分母是 N!,每个连续项添加的数量会缩小非常 em> 很快。使用简单求和,您可以很快达到一个点,即您添加的值足够小,不再影响总和。

在欧拉常数的特定情况下,由于数字不断减少,我们可以更好地处理它们的一种方法是计算和存储所有项,然后以相反的顺序将它们相加。

另一种更普遍的可能性是改用 Kahan 的求和算法。这会在进行求和时跟踪运行错误,并在添加每个连续项时将当前错误考虑在内。

例如,我重写了您的代码以使用 Kahan 求和来计算(大约)典型(80 位)long double 的精度极限:

#include<iostream>
#include<cstdlib>
#include<math.h>
#include <vector>
#include <iomanip>
#include <limits>

// Euler's number

using namespace std;

long double factorial(long double n)
{
    long double result = 1.0L;
    for(int i = 1; i <= n; i++)
    {
        result = result*i;
    }
    return result;
}

template <class InIt>
typename std::iterator_traits<InIt>::value_type accumulate(InIt begin, InIt end) {
    typedef typename std::iterator_traits<InIt>::value_type real;
    real sum = real();
    real running_error = real();

    for ( ; begin != end; ++begin) {
        real difference = *begin - running_error;
        real temp = sum + difference;
        running_error = (temp - sum) - difference;
        sum = temp;
    }
    return sum;
}

int main()
{  
  std::vector<long double> terms;
  long double epsilon = 1e-19;

  long double i = 0;
  double term;
 
  for (int i=0; (term=1.0L/factorial(i)) >= epsilon; i++)
    terms.push_back(term);

  int width = std::numeric_limits<long double>::digits10;

  std::cout << std::setw(width) << std::setprecision(width) << accumulate(terms.begin(), terms.end()) << "\n";
}

结果:2.71828182845904522

公平地说,我实际上应该补充一点,我没有使用简单的求和检查您的代码会发生什么——您看到的问题可能来自其他来源。另一方面,这确实非常适合 Kahan 求和至少有合理机会改善结果的情况。

【讨论】:

    【解决方案2】:
    #include<iostream>
    #include<cmath>
    #include<iomanip>
    
    #define EPSILON  1.0/10000000
    #define AMOUNT  6
    
    using namespace std;
    
    int main() {
        
        long double e = 2.0, e0;
        long double factorial = 1;
    
        int counter = 2;
        long double moduloDifference;
    
        do {
            e0 = e;
            factorial *= counter++;
            e += 1.0 / factorial;
    
            moduloDifference = fabs(e - e0);
        } while (moduloDifference >= EPSILON);
    
        cout << "Wynik:" << endl;
        cout << setprecision(AMOUNT) << e << endl;
        return 0;
    }
    

    这是一个优化版本,没有单独的函数来计算阶乘。

    问题 1:我仍然不确定 EPSILON 如何管理精度。

    问题 2:我不明白 long double 和 double 之间的真正区别。关于我的代码,为什么 long double 需要小数点(1.0/someNumber),而 double 不需要(1/someNumber)

    【讨论】:

    • 默认1.0double。如果你想要float 使用1.0f1.0F 并且long double1.0l1.0L。因此EPSILON 将是double。顺便说一句,您应该从使用 C 风格的 #define 切换到 constexpr 语句。 注意 fabs uses double.
    • 问题 2 应该不是问题。如果1/factorial 不起作用,那么它是一个编译器错误。你用的是什么编译器?
    • @JHBonarius gcc 编译器
    猜你喜欢
    • 1970-01-01
    • 2017-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-07
    • 2013-09-17
    • 1970-01-01
    相关资源
    最近更新 更多