2010-07-14 302 views
7

CLR中的内部三角函数的不准确性让我非常恼火。众所周知,C#中Math.Sin()和Math.Cos()的准确性

Math.Sin(Math.PI)=0.00000000000000012246063538223773 

而不是0.类似的情况发生在Math.Cos(Math.PI/2)

但是当我做了一长串的计算方法,在特殊的情况评估为

Math.Sin(Math.PI/2+x)-Math.Cos(x) 

的,结果是零为X = 0.2,但对于x = 0.1不为零(尝试)。另一个问题是,当争论的数量很大时,不准确度会相应增大。

所以我想知道是否有人编写了一些更好的C#中的trig函数代表与世界共享。 CLR是否调用一些实现CORDIC或类似的标准C数学库?链接:wikipedia CORDIC

+8

你认为pi的表现是多么准确? – 2010-07-14 19:31:08

+3

如果你想要象征性的数学,做符号数学。如果你使用浮点类型,你会得到有限的精度。 – AakashM 2010-07-14 19:45:44

+4

-1对于不“做家庭作业”,也认为'System.Math'是C#的一部分(提示:它是C#的一部分。NET框架)。 – 2010-07-14 19:49:52

回答

18

这与三角函数的精度无关,但更多与CLS类型系统。根据文档double有15-16位精度(这正是你得到的),所以你不能更精确的这种类型。所以如果你想要更高的精度,你需要创建一个能够存储它的新类型。

还要注意,你不应该写一个这样的代码:

double d = CalcFromSomewhere(); 
if (d == 0) 
{ 
    DoSomething(); 
} 

你应该这样做,而不是:

double d = CalcFromSomewhere(); 
double epsilon = 1e-5; // define the precision you are working with 
if (Math.Abs(d) < epsilon) 
{ 
    DoSomething(); 
} 
6

这是浮点精度的结果。你可以得到一定数量的有效数字,任何不能精确表示的东西都是近似的。例如,pi不是一个有理数,所以不可能得到一个确切的表示。既然你不能得到pi的确切值,你不会得到包括pi在内的数字的正弦和余弦(大多数情况下你也不会得到正弦和余弦的确切值)。

最好的中间解释是"What Every Computer Scientist Should Know About Floating-Point Arithmetic"。如果你不想进入这个问题,只要记住浮点数通常是近似值,浮点计算就像在地上移动一堆沙子一样:你用它们做的每件事都会让你失去一点沙子,拿起一点污垢。

如果你想要精确的表示,你需要找到自己的符号代数系统。

+0

我知道PI没有完全由于IEEE-754算法而定义,我希望我可以用C#驱动一个符号代数系统,但现在我不能这样做。 – ja72 2010-07-14 20:04:28

9

我听到你的声音。我对分区的不准确感到非常恼火。有一天我做了:

Console.WriteLine(1.0/3.0); 

和我得到了0.333333333333333,而不是正确的答案是0。3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3333333333333333 ...

也许现在你看到了什么问题。 Math.Pi不等于pi任何超过1.0/3.0等于三分之一。它们都与真实值相差数百万亿分之一,因此使用Math.Pi或1.0/3.0执行的任何计算也将会减少数百万亿分之一,包括采用正弦。

如果你不喜欢那近似算术是近似那么不要使用近似算术。使用精确的算术。当我需要精确算术时,我曾经使用滑铁卢枫叶;也许你应该购买一份。

+0

出于好奇,如果想要在C#中计算精确的算术,是否有可能?只是不知道可以用来做什么?连小数都不会削减它,对吧? – 2010-07-14 23:47:56

+2

@Joan:是的,我们可以很容易地用整数,甚至对于有理数(只是将分子/分母存储为大整数),并将规则放入我们的库中,以获得我们想要的任何特定实数:pi,e,square-根等等。但是,对任何*可以想象的实数进行任意精度算术的库是不可能的;即使假设你有某种存储方式*(比如说一个公式,它会给我们一个任意数字的数字)*,但在计算上是不可能的,即使比较两个任意实数的平等也是不可能的! – 2010-07-15 00:53:38

+1

@BlueRaja:的确如此。通常情况下,你在这种情况下所做的就是象征性地操纵算术量;你有一个pi的符号和一个符号e,就像你有一个符号1​​,2,3一样,然后你编码所有的算术和三角身份,就像pi的正弦为零,依此类推。符号数学很难。 – 2010-07-15 14:11:12

1

我反对这个想法的错误是由于四舍五入。有什么可以做的就是定义sin(x)如下,利用泰勒展开有6项:

const double π=Math.PI; 
    const double π2=Math.PI/2; 
    const double π4=Math.PI/4; 

    public static double Sin(double x) 
    { 

     if (x==0) { return 0; } 
     if (x<0) { return -Sin(-x); } 
     if (x>π) { return -Sin(x-π); } 
     if (x>π4) { return Cos(π2-x); } 

     double x2=x*x; 

     return x*(x2/6*(x2/20*(x2/42*(x2/72*(x2/110*(x2/156-1)+1)-1)+1)-1)+1); 
    } 

    public static double Cos(double x) 
    { 
     if (x==0) { return 1; } 
     if (x<0) { return Cos(-x); } 
     if (x>π) { return -Cos(x-π); } 
     if (x>π4) { return Sin(π2-x); } 

     double x2=x*x; 

     return x2/2*(x2/12*(x2/30*(x2/56*(x2/90*(x2/132-1)+1)-1)+1)-1)+1; 
    } 

典型的错误是1e-16和最坏的情况是1e-11。它比CLR更糟糕,但它可以通过添加更多条款来控制。好消息是,对于OP中的特殊情况和Sin(45°)的答案是准确的。

+0

关于哪些角度具有精确的三角关系的相关文章。值http://math.stackexchange.com/q/176889/3301 – ja72 2013-12-20 23:17:59

+0

如果唯一的问题是OP的特殊情况,那么最好把它们写成if-else语句,而不是用这样的解决方案。这些方法可以在直接用例如零,但不是近似值。如果例如你以前面操作的近似结果称Sin,它不起作用(例如,因为x非常小,但不完全是零 - 即使它“应该”)。这里真正的问题是使用double和期望得到确切的结果,世界上没有算法或公式可以解决。 – enzi 2014-01-25 18:50:17