C#中Math.Sin()和Math.Cos()的准确性

时间:2010-07-14 19:25:55

标签: c# clr cosine

我对CLR中内在触发功能的不准确性感到非常恼火。众所周知,

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

6 个答案:

答案 0 :(得分: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();
}

答案 1 :(得分:9)

我听到了你的声音。我对分裂的不准确感到非常恼火。前几天我做了:

Console.WriteLine(1.0 / 3.0);

和我0.333333333333333,而不是正确的答案是0.333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 ...

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

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

答案 2 :(得分:6)

这是浮点精度的结果。您可以获得一定数量的有效数字,并且任何无法准确表示的数字都是近似的。例如,pi不是一个有理数,因此不可能得到精确的表示。由于你无法得到精确的pi值,你不会得到包括pi在内的数字的精确正弦和余弦(在大多数情况下你也不会得到正弦和余弦的精确值)。​​

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

如果你想要精确的表示,你需要找到一个象征性的代数系统。

答案 3 :(得分:2)

您需要使用任意精度的十进制库。 (。Net 4.0有一个arbitrary integer class,但不是十进制)

有几种流行的可用:

答案 4 :(得分: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°),答案是准确的。

答案 5 :(得分:1)

我们目前正弦和余弦的实现是

    public static double Sin(double d) {
        d = d % (2 * Math.PI); // Math.Sin calculates wrong results for values larger than 1e6
        if (d == 0 || d == Math.PI || d == -Math.PI) {
            return 0.0;
        }
        else {
            return Math.Sin(d);
        }
    }

    public static double Cos(double d) {
        d = d % (2 * Math.PI); // Math.Cos calculates wrong results for values larger than 1e6
        double multipleOfPi = d / Math.PI; // avoid calling the expensive modulo function twice
        if (multipleOfPi == 0.5 || multipleOfPi == -0.5 || multipleOfPi == 1.5 || multipleOfPi == -1.5) { 
            return 0.0;
        }
        else {
            return Math.Cos(d);
        }
    }