2016-01-15 39 views
2

我有这个函数写在C#中来计算sin(x)。但是当我用x = 3.14进行尝试时,sin X的打印结果是NaN(不是数字),但是在调试时,它非常接近0.001592653 值不是太大,也不是太小。那么NaN怎么会出现在这里呢?为什么这个sin(x)函数在C#中返回NaN而不是数字

static double pow(double x, int mu) 
     { 
      if (mu == 0) 
       return 1; 
      if (mu == 1) 
       return x; 
      return x * pow(x, mu - 1); 
     } 

     static double fact(int n) 
     { 
      if (n == 1 || n == 0) 
       return 1; 
      return n * fact(n - 1); 
     } 

     static double sin(double x) 
     { 
      var s = x; 

      for (int i = 1; i < 1000; i++) 
      { 
       s += pow(-1, i) * pow(x, 2 * i + 1)/fact(2 * i + 1); 
      } 
      return s; 
     } 

     public static void Main(String[] param) 
     { 
      try 
      { 
       while (true) 
       { 
        Console.WriteLine("Enter x value: "); 
        double x = double.Parse(Console.ReadLine()); 
        var sinX = sin(x); 
        Console.WriteLine("Sin of {0} is {1}: " , x , sinX); 

        Console.ReadLine(); 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex.Message); 
      } 
     } 
+0

什么样的价值是X?我使用的值为1,它出来为0.8414709848078965 - 解释重现问题的步骤.. –

+0

我使用x = 3.14,它是使错误发生 – Andiana

回答

3

它失败,因为这两个pow(x, 2 * i + 1)fact(2 * i + 1)最终返回Infinity

在我的情况下,这是x = 4,i = 256

注意pow(x, 2 * i + 1) = 4^(2 * 257) = 2.8763090157797054523668883052624395737887631663 × 10^309 - 一个愣神大量这仅仅是在双重的最大值,这大约是1.79769313486232×10^308

您可能会感兴趣的只是用Math.Sin(x)

另请注意,fact(2 * i + 1) = 513! =an even more ridiculously large number这比estimated number of atoms in the observable universe10^1000倍。

+0

好吧,可能是我必须改变算法:) – Andiana

+1

我想为基准编写此函数,而不是为了计算:) – Andiana

+0

使用ILSpy并查看Math.Sin的内部实现。 –

2

当x == 3.14和我== 314,那么你得到无限:

?pow(-1, 314) 
1.0 
?pow(x, 2 * 314 + 1) 
Infinity 
? fact(2 * 314 + 1) 
Infinity 
0

这里的问题是浮动的“真正的”数点表示的理解。

允许大范围值的双数字只有15到17位十进制数的精度。

在这个例子中,我们计算和-1之间的值1.

我们通过使用它的级数展开这基本上是项的总和计算sin函数的值。在这种扩张中,随着我们的发展,这些术语变得越来越小。

当条件达到小于1e-17的值时,将它们添加到已经存在的值不会有任何区别。这是因为我们只有52位的精度,当我们达到一个小于1e-17的时间时就用完了。

因此,而不是做一个恒定的1000环的,你应该做这样的事情:

static double sin(double x) 
    { 
     var s = x; 

     for (int i = 1; i < 1000; i++) 
     { 
      var term = pow(x, 2 * i + 1)/fact(2 * i + 1); 

      if (term < 1e-17) 
       break; 

      s += pow(-1, i) * term; 
     } 
     return s; 
    }