泰勒级数在 sin(90) 和 cos(120) 之后得到 nan

Taylor Series Resulting in nan after sin(90) and cos(120)

正在做一个学校项目。我不明白为什么在 sin(90)cos(120).

之后罪会出现在 -NaN

谁能帮我理解一下?

此外,当我将它放入在线 C++ 编辑器中时,它完全可以工作,但在 linux 中编译时却没有。

// Nick Garver
// taylorSeries
// taylorSeries.cpp
#include <iostream>
#include <cmath>
#include <iomanip> 

using namespace std;

const double PI = atan(1.0)*4.0;
double angle_in_degrees = 0;
double radians = 0;
double degreesToRadians(double d);
double factorial(double factorial);
double mySine(double x);
double myCosine(double x);

int main()
{
    cout << "3[2J3[1;1H";
    cout.width(4); cout << left << "Deg";
    cout.width(9); cout << left << "Radians";
    cout.width(11); cout << left << "RealSine";
    cout.width(11); cout << left << "MySin";
    cout.width(12); cout << left << "RealCos";
    cout.width(11); cout << left << "MyCos"<<endl;

    while (angle_in_degrees <= 360) //radian equivalent of 45 degrees
    {
        double sine = sin(degreesToRadians(angle_in_degrees));
        double cosine = cos(degreesToRadians(angle_in_degrees));
        //output
        cout.width(4); cout << left << angle_in_degrees;
        cout.width(9); cout << left << degreesToRadians(angle_in_degrees);
        cout.width(11); cout << left << sine;
        cout.width(11); cout << left << mySine(degreesToRadians(angle_in_degrees));
        cout.width(12); cout << left << cosine;
        cout.width(11); cout << left << myCosine(degreesToRadians(angle_in_degrees))<<endl;
        angle_in_degrees = angle_in_degrees + 15;
    }
    cout << endl;
    return 0;
}


double degreesToRadians(double d)
{
    double answer;
    answer = (d*PI)/180;
    return answer;
}

double mySine(double x)
{
    double result = 0;
    for(int i = 1; i <= 1000; i++) {
        if (i % 2 == 1)
            result += pow(x, i * 2 - 1) / factorial(i * 2 - 1);
        else
            result -= pow(x, i * 2 - 1) / factorial(i * 2 - 1);
    }
    return result;
}

double myCosine(double x)
{
    double positive = 0.0;
    double negative= 0.0;
    double result=0.0;
    for (int i=4; i<=1000; i+=4)
    {
        positive = positive + (pow(x,i) / factorial (i));
    }
    for (int i=2; i<=1000; i+=4)
    {
        negative = negative + (pow(x,i) / factorial (i));
    }
    result = (1 - (negative) + (positive));
    return result;
}

double factorial(double factorial)
{
    float x = 1;
    for (float counter = 1; counter <= factorial; counter++)
    {
        x = x * counter;
    }
    return x;
}

在我回答这个问题之前,先说几点:

  • 保持代码整洁对您自己的调试总是有帮助的。删除不必要的空行,确保括号样式统一,并适当缩进。我这样做是为了你,但相信我,如果你保持一致的风格,你会避免很多错误!
  • 你有函数将 double 作为输入和 return double,但在内部只使用 float;那应该是一个危险信号!
  • 你的整个 degreesToRadians 会更好读,如果你只使用 return (d*PI)/180;
  • 则只有三分之一长

现在回答:

在您的 factorial 函数中,您计算​​了高达 1999 的值的阶乘。提示:尝试计算出 1999! 的值并查找 float 在您的机器上可以容纳的最大数量。然后查找 double 的最大值。 1999! 大了多少个数量级?

1999 年!大约是10^5732。这是一个很大的数字,比 32 位浮点数大 150 个数量级,或者比 32 位浮点数大 18 个数量级 64bit double 可以装。相比之下,将 1999! 存储在双精度数中就像试图将太阳中心到地球中心的距离拟合到典型的 0.1µm 直径的细菌中。

(Marcus有好点子,我要往别的方向说了……)

看看泰勒级数中的项。在不到 10 个学期后,它们变得太小而无法产生任何影响。要1000就是自找麻烦

不要去 1000,去直到下一个学期不添加任何东西,比如:

term = pow(x, i * 2 - 1) / factorial(i * 2 - 1);
if (result + term == result) { break; }
result += term;

如果您迭代计算 powfactorial 而不是每次都重新开始,则该系列会 运行 快得多。 (但是,此时速度可能不是问题。)

Float 具有 24 位二进制精度。可能从 13! 开始,您将在浮点数中出现舍入误差。另一方面,Double 具有 53 位精度,并且会持续到大约 22 位!没有舍入误差。我的观点是你应该在 double.

中完成 factorial()

另一个问题是,对于更大的参数,泰勒级数的计算有点 'unstable'。中间项变得比最终结果大,从而导致其他舍入误差。为避免这种情况,计算正弦和余弦的常用方法是首先折叠到 -45 到 +45 度之间。以后不需要展开,除了标志之外。

至于为什么您在一个系统上遇到问题而在另一个系统上没有问题 -- 不同的实现对 NaN 的处理方式不同。

一旦您排除了 NaN,请尝试以相反的顺序计算该系列。这将导致一组不同的舍入误差。它会让你的 sin() 更接近真正的 sin 吗?

'real' sin 可能是使用 64 位定点算法在硬件中计算的,并且在超过 99% 的时间里会 "correctly rounded" 到 53 或 24 位。 (当然,这取决于芯片制造商,因此我的 'hand-waving' 声明。)

要判断'close'你的价值如何,你需要计算ULPs(最后一位的单位)。这涉及查看 float/double 中的位。 (超出本题范围。)

对 TMI 感到抱歉。