【问题标题】:Output is nan while it shouldn't be输出是 nan 而它不应该是
【发布时间】:2021-12-01 09:02:30
【问题描述】:

我需要计算以下内容:

S= 1- x^2 / 2! + x^4 / 4! - x^6 / 6! + ... + (-1)^n * x^2n / (2n)!

其中 n 介于 1 到 100 之间,x 是双精度数。

我有以下代码:

unsigned int factorial (unsigned int n)
{
    if (n == 0)
        return 1;
    return n * factorial(n - 1);
}

double exFive(int n, double x)
{
    double s = 1;

    for (int i = 1; i <= n; ++i)
    {
        int j = 2 * i; 
        s = s + pow(-1, i) * pow(x, 2*i) / factorial(j); //problem is here I guess
    }

    return s;
}

void fiveIO()
{
    int n = 1;
    double x;
   
    cout << "Input n: ";
    cin >> n;
    while ((n < 1) || (n > 100))
    {
        cout << "Wrong number, input again: ";
        cin >> n;
    } 

    cout << "Input: ";
    cin >> x;

    cout << "Result is " << fixed << setprecision(2) << exFive(n, x);
}

它可以工作,但是结果是 nan,其中 n 高于 ~15.. 但我不知道为什么。 我猜是 FiveX 功能。

所以,例如,n = 3, x = 5 输出 -7.16(这是正确的),但 n = 50, x = 5 输出“nan”.. 是因为输出的数字太大了吗?但是那我该怎么做呢?

【问题讨论】:

  • 典型的32位int只能容纳factorial(12)的结果,任何更大的n都会导致整数溢出。 NaNfloat 除以 0 的结果。
  • 而 64 位数字只能处理 ~20!
  • 200!有 375 位数字:如果你想暴力破解这个问题,你将需要一些扩展精度库,例如 GMP
  • @Yksisarvinen 我以为NAN是由0.0/0.0inf-inf生成的
  • @MatG 嗯,是的,你是对的。除以零应该产生inf,0.0 / 0.0,我的错。

标签: c++ nan


【解决方案1】:

避免intfactorial(j) Typical 32-bit int can only hold result of factorial(12) 中溢出(未定义的行为 (UB))。

通过根据之前的值计算term 来改进循环计算。

//for (int i = 1; i <= n; ++i) {
//    int j = 2 * i; 
//    s = s + pow(-1, i) * pow(x, 2*i) / factorial(j); //problem is here I guess
//}

// Something like
double xx = x*x;
double term = 1.0;
for (int i = 1; i <= n; ++i) {
    int j = 2 * i; 
    term *= -xx / ((j-1)*j);
    s += term;
}

更高级的方法是反向计算总和以最小化计算错误,但很大程度上取决于x 的允许范围,这不受 OP 的限制。

【讨论】:

  • 我试过了,对于 n = 50,51,52,53 等和 x = 5,它一直输出 0.28。例如,如果我将 x 更改为 6,则输出更改为 0.96,但仍然当 n 改变时重复。除非我解释错了。
  • @danee 好的,“对于 n = 50,51,52,53 等,以及 x = 5,输出 0.28 是什么预期。”。那输出不正确吗?对于适度的x,该系列会收敛,因此我不希望有更多的术语来改变结果。 (请务必使用此答案的最新版本)
  • Touché,我只是认为这是不对的。老实说,我什至不知道如何检查它,除非我在纸上计算它需要一些时间。我只是不明白为什么它会重复自己,因为一轮减去,一轮加到总和(因为“(-1)^n”)。所以我猜它应该会波动。
  • @danee 是的,但是与sum 相比,term 的波动可能很小,以至于sum += term 不会改变值。提示尝试setprecision(20) 以获得更多信息。
  • @danee 它是否适用于“n = 3, x = 5 输出 -7.16(正确)”?
【解决方案2】:

对于公式:

S= 1- x^2 / 2! + x^4 / 4! - x^6 / 6! + ... + (-1)^n * x^2n / (2n)!

我提交的这个实现的规模略好一些,因为它避免了一次将所有内容相乘。

double exFive(int n, double x)
{
    double s = 1;

    bool subtract = true;

    for (int i = 1; i <= n; i++)
    {
        double inner = 1;
        int n2 = i * 2;
        for (int j = 1; j <= n2; j++)
        {
            inner *= (x / j);
        }

        if (subtract)
        {
            s = s - inner;
        }
        else
        {
            subtract = s + inner;
        }
        subtract = !subtract;
    }
    return s;
}

示例运行:

Input n: 100
Input: 94
Result is -4417.00

上面的实现,当它需要计算时说(x^n)/(n!)。而不是计算

T  = (x*x*x*x....*x*x*x) / (1*2*3*4*5*6*....*(N-1)*N)`

在上面的表达式中,当尝试使用powfactorial 函数进行计算时,分子和分母都会溢出。因此,它会将其计算为:

T = (x/1) * (x/2) * (x/3) * (x/4) * (x/5) * (x/6)

这避免了使用阶乘和 pow 函数。而且由于外循环的每次迭代都是交替进行减法或加法运算,溢出的机会仍然较小。

【讨论】:

  • 是的,但是对于 n = 3,x = 5,它输出 -11.5,尽管它应该是 -7.16(练习以 i/o 为例)
猜你喜欢
  • 2020-07-22
  • 2017-01-17
  • 2019-11-09
  • 1970-01-01
  • 2018-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-14
相关资源
最近更新 更多