泰勒系列导致sin(90)和cos(120)之后的nan

时间:2016-02-07 22:05:03

标签: c++ trigonometry nan

做一个学校项目。我不明白为什么在-NaNsin(90)之后,cos(120)犯了罪。

任何人都可以帮我理解这个吗?

另外,当我把它放在在线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 << "\033[2J\033[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;
}

2 个答案:

答案 0 :(得分:2)

(马库斯有好点;我会在其他方向漫步......)

查看泰勒系列中的术语。在不到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()。

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

至于为什么你在一个系统上遇到麻烦而在另一个系统上遇到麻烦 - 不同的实现以不同的方式处理NaN。

一旦你完成了NaN,尝试以相反的顺序计算系列。这将导致一组不同的舍入误差。它会使你的罪()更接近真正的罪吗?

真实的&#39; sin可能是用64位定点算法在硬件中计算的,并且将被正确舍入&#34;超过99%的时间到53或24位。 (当然,这取决于芯片制造商,因此我的“挥手”声明。)

判断&#39;关闭&#39;你的价值是,你需要计算ULP(最后一个单位)。这涉及查看float / double中的位。 (超出了这个问题的范围。)

对TMI感到抱歉。

答案 1 :(得分:1)

在我回答这个问题之前,请注意几点:

  • 保持代码整洁,这对您自己的调试总是有帮助的。删除不必要的空行,确保包围样式均匀,并正确缩进。我为你做了这个,但是相信我,如果你保持一贯的风格,你会避免很多错误!
  • 您的功能以double作为输入并返回double,但内部只使用float;那应该是一面红旗!
  • 如果您刚刚使用degreesToRadians
  • ,您的整个return (d*PI)/180;会更好阅读,只有三分之一

现在回答:

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

1999年!是约10 ^ 5732。这是一个很大的数字,比32位浮点数能够容纳的 150个数量级,或者仍然比64位双倍容量<强> 18个数量级。比较一下,将 1999!存储在一个双倍中就像试图在典型的0.1μm直径的细菌中拟合从太阳中心到地球中心的距离。