【问题标题】:Subtraction Doubles in C++ [closed]C ++中的减法双倍[关闭]
【发布时间】:2012-10-13 03:29:00
【问题描述】:

我正在做一项家庭作业,我需要使用 McLaurin 系列来评估 cosh(x) 和 sin(x)。每次程序运行时,用户输入一个从 0 到 10 的数字来指定要评估系列的项数。然后,用户输入 x 的值以进行评估。一旦用户点击输入,将计算并打印出该系列的 10 个增量。此外,我还必须找到确切的错误。为此,我为 sinh 和 cosh 创建了几个不同的函数,用于执行特定项的计算。例如:下面的代码将术语评估为 10!。

void cosFunction10(double power, double value)
{
     double cosh_x = 0;
     double math_cosh = 0;
     double exact_error;
     double x;

     if (value < 0)
         x = -0.1*0;

     for (int i = 0; i < 11; ++i)
     {
         cosh_x = (1.0 + ((x*x)/(2.0*1.0)) + ((x*x*x*x)/(4.0*3.0*2.0*1.0)) + ((x*x*x*x*x*x)/(6.0*5.0*4.0*3.0*2.0*1.0)) + ((x*x*x*x*x*x*x*x)/(8.0*7.0*6.0*5.0*4.0*3.0*2.0*1.0)) + ((x*x*x*x*x*x*x*x*x*x)/(10.0*9.0*8.0*7.0*6.0*5.0*4.0*3.0*2.0*1.0)));
         math_cosh = cosh(x);
         exact_error = (math_cosh - cosh_x);

         cout << setiosflags(ios::scientific) << setprecision(3) << x << "\t";
         cout << setiosflags(ios::scientific) << setprecision(5) << cosh_x << "\t";
         cout << math_cosh << "\t";
         cout << exact_error << endl;
         x = x + (0.1*value);
     }
 }

如果我运行程序并输入以下值:10(第 n 项)和 -6(x 的值)。

这是我应该得到的: (第二个增量)

 x                Series            Exact (using the cmath functions)    Exact % Error
 -6.000e-001      1.18547e+000      1.18547e+000                        0.00000e+000

接下来的 2 个输出对于确切的错误将是相同的,直到我到达第 4 个增量

 x                Series            Exact (using the cmath functions)    Exact % Error
 -1.800e+100      3.10747e+000      3.10747e+000                        -7.67243e-005

当我运行我的代码时,我没有得到上述结果: (第二个增量)

 x                Series            Exact (using the cmath functions)    Exact % Error
-6.000e-001      1.18547e+000      1.18547e+000                         4.55325e-012

所以似乎出了点问题,因为我的确切错误与我得到的错误甚至不一样。我知道可能会有稍微不同的值,这是预期的,但不会达到这个程度。

任何建议将不胜感激,

谢谢

【问题讨论】:

  • 到底是什么让你相信 cosh(x) 等于 1 + x^2/2?
  • 您要求我们在您未向我们展示的代码中查找错误。该错误在实际功能中。
  • “问题是减法计算不正确”。我向您保证,问题出在您的逻辑上,而不是双精度数的减法上。如果您不发布您的实际逻辑,没有人可以帮助您。
  • 你的 for 循环做了 11 次完全相同的事情。另外,为什么2.0*1.0 而不仅仅是2.0
  • 现在与 0.00000000000455325 相同。见en.wikipedia.org/wiki/Scientific_notation

标签: c++ floating-point subtraction


【解决方案1】:

一定会有这样的区别,因为cosh(x) 是根据下面给出的公式以不同的方式定义的:

请参阅here 了解更多详情。

但是,如果您基于 MacLaurin 级数的计算来计算金额,那么问题在于您没有以正确的方式累积值。下面的实现可能是你真正需要的:

void cosFunction(double x)
{
    double cosh_x = 1;
    double math_cosh;
    double exact_error;
    double factorial = 1;
    double x_pow = 1;

    for (int i = 0; i < 11; ++i) {
            cosh_x = x_pow/factorial;
            x_pow = x*x*x_pow; 
            factorial = factorial* (2 * i + 1) * (2 * i + 2);
    }

    math_cosh = cosh(x);
    exact_error = (math_cosh - cosh_x);
    cout << setiosflags(ios::scientific) << setprecision(5) << exact_error << endl;

}

【讨论】:

  • 我知道,但我的任务是使用 McLaurin 级数来评估 cosh(x)。所以我之前已经完成了数学部分,我现在只是编码它是正确的,因为我已经和我的老师确认了它。
  • 好的,问题是你没有累积错误。
  • 但是如果我用 cosh_x 减去 math_cosh ,应该会产生准确的值。我只想获得每次迭代的确切值
  • 我的理解是整个计算都是错误的。请使用修改后的代码查看我的更新。
【解决方案2】:

正如 cmets 所揭示的,您得到的误差实际上非常小,小于 10-11。此外,您必须考虑到您计算 cosh 的方式与内置函数完成的方式并不完全相同。这两种方法最终都是近似值,但它们的工作方式不同,因此它们产生的舍入误差和近似误差会略有不同。

你不能期望错误是“flat 0”。

【讨论】:

  • 我明白你在说什么,我并不想难为情。我只是有一个特定的任务,输出应该与给定的内容完全相同,而给定的是它的平 0。但我会想出别的东西。感谢您的所有帮助和耐心
  • 在这种情况下,要问的明显问题是:内置函数是否使用 McLaurin 级数?,然后:需要多少次迭代我>?但即使你弄清楚了,你的舍入误差可能仍然与内置误差不同。比较浮点数是很困难的,你永远不能指望它们之间的差是 0:stackoverflow.com/questions/10334688/…(坦率地说,如果赋值如你所说,那么这个赋值就有问题。)
  • 好吧,为了更清楚地解释它,赋值的目的是让用户输入一个术语来评估系列,然后输入一个 x 的值。然后它通过从 0 开始的 10 个增量来完成。而且我有多个函数用于不同的增量。所以前几个增量应该等于0。我要问我的老师,根据你的cmets。所以非常感谢你更清楚地解释事情。
【解决方案3】:

在数学意义上,正确答案可能是零。但在实际计算意义上,它不会是零:

想象一下,如果您使用固定的十进制精度进行算术运算,比如 8 位。您可以将1.0/3.0 表示为.33333333。但这会使3.0 * (1.0/3.0) 变成3.0 * .33333333,即.999999999。因此,虽然在数学意义上,1.0 - (3.0 * (1.0 / 3.0)) 应该为零,并且具有固定的小数精度,但您很可能会得到类似 .00000001 的东西。

相同的规则适用于固定二进制精度,这是您使用double 时得到的。

【讨论】:

    【解决方案4】:
    //Part of the problem is that in the line after the 'for' nothing changes (typo?.) The
    //following works for x = 0.5`
    //Note that the loop works properly for all terms including the first which is 1.0
    
    #include <iostream>
    #include <math.h>
    
    using namespace std;
    
    int factorial(int n)
    {
        return (n == 1 || n == 0) ? 1 : factorial(n - 1) * n;
    }
    
    int main()
    {
        // use taylor series to approximate cosh(x) - use 7 terms
        double x = 0.5;
        double top, bot;
        double s = 0.0;
    
        for (int i = 0; i < 7; i++)
        {
            top = pow(x,double(i)*2.0);
            bot = double(factorial(i*2));
            s  += (top / bot);
        }
        cout << "myCosh     " << s << endl;
        cout << "cosh       " << cosh(x)<< endl;
        cout << "difference " << fabs(s - cosh(x)) << endl;
        return 0;
    }
    

    【讨论】:

      【解决方案5】:

      我使用cout 操作运行您的函数,修改如下:

      cout << setiosflags(ios::fixed) << setprecision(12);
      cout << "for x="<<x<<": "<<math_cosh<<" - "<<cosh_x<<" = "<<exact_error<<"\n";
      

      我为value 选择了我自己的值,因为您没有说明您使用的是什么。

      我得到以下结果,这表明math_coshcosh_x 相同。您的问题似乎是您没有看到足够的数字来注意到它们是不同的。

      for x=0.003233200000: 1.000005226796 - 1.000005226791 = 0.000000000005
      for x=0.006466400000: 1.000020907237 - 1.000020907164 = 0.000000000073
      for x=0.009699600000: 1.000047041489 - 1.000047041120 = 0.000000000369
      for x=0.012932800000: 1.000083629824 - 1.000083628658 = 0.000000001166
      for x=0.016166000000: 1.000130672624 - 1.000130669778 = 0.000000002846
      for x=0.019399200000: 1.000188170381 - 1.000188164480 = 0.000000005901
      for x=0.022632400000: 1.000256123697 - 1.000256112765 = 0.000000010932
      for x=0.025865600000: 1.000334533282 - 1.000334514632 = 0.000000018650
      for x=0.029098800000: 1.000423399955 - 1.000423370081 = 0.000000029875
      for x=0.032332000000: 1.000522724646 - 1.000522679112 = 0.000000045534
      for x=0.035565200000: 1.000632508392 - 1.000632441726 = 0.000000066667
      

      关于家庭作业,使用麦克劳林级数计算 cosh(x):你的函数,如所写,不是很精确,因为它只计算系列 1 + 0 + x*x/2 的前三个项。您不是在计算 x^n 或 n!超过 n=2。要生成更精确的结果,您需要积累更多的系列术语 - 如下所示...

      int NUMBER_OF_TERMS=10; // the number of terms to compute (including zero terms)
      for ( int i=0; i<NUMBER_OF_TERMS; i++ )
         {
         cosh_x += (i%2==0?1:0)*term; // even derivatives are 1, odd derivatives are 0
         term *= x/(i+1); // apply incremental power and factorial
         }
      

      这仍然不能保证准确性。由于舍入误差,您无法确保完全匹配内置函数 cosh(x) ,除非您完全复制其算法。这两个函数只会产生一个近似值,因为对于大多数输入值,纯数学输出将是一个超越数——而这样的数字不能在 double 中精确表示。

      您还应该记住,麦克劳林级数是基于多项式的近似值,在 x=0 附近最准确。正如多项式的本质一样,不完整的求和最终可能(随着 x 远离 0)与正确答案非常迅速地偏离。这就是为什么在上面的示例输出中,x 的值越小,错误值就越小。


      关于科学记数法与定点精度:

      科学记数法向您显示数字的最重要的非零数字 - 即使这些数字从小数点右侧的 12 位开始。阅读科学记数法时,请务必注意e 之后的信息,因为这描述了数字的真实大小。 double-precision 值可以存储大约 20 位十进制数字的真实信息。这对于进行多次重复计算很有用,但在比较两个数字是否相等时通常意义不大。

      尽管这可能不在您的任务范围内,但您可以做的是限制差异的定点精度。以下是您可以如何执行此操作的示例:

      const double PRECISION=1e-4; // indicates the number of fractional digits to keep
      exact_error = round(exact_error/PRECISION)*PRECISION; // limit precision
      

      ...或者只检查接近零的情况可能更简单...

      if ( fabs(exact_error)<PRECISION/2 ) exact_error = 0; // close enough to zero
      

      【讨论】:

      • cplusplus.com - std::fixed 表明 iostreams 的 floatfield 格式化标志有三个不同的选项。它们是fixedscientific(none)。它们中的每一个都以自己的方式与precision 值交互。
      猜你喜欢
      • 1970-01-01
      • 2014-02-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-21
      相关资源
      最近更新 更多