代码之家  ›  专栏  ›  技术社区  ›  John Alexiou

c中math.sin()和math.cos()的准确性#

  •  7
  • John Alexiou  · 技术社区  · 14 年前

    对于clr中固有的trig函数的不精确性,我非常恼火。众所周知

    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 回复  |  直到 6 年前
        1
  •  2
  •   BlueRaja - Danny Pflughoeft    14 年前

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

    有几种流行的:

        2
  •  18
  •   Darin Dimitrov    14 年前

    这与三角函数的精度无关,而与CLS类型系统有关。根据文件A 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();
    }
    
        3
  •  9
  •   Eric Lippert    14 年前

    我听见了。我对分歧的不准确感到非常恼火。前几天我做了:

    Console.WriteLine(1.0 / 3.0);
    

    我得到了0.33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333……

    也许现在你看到了问题所在。 math.pi不等于pi 超过1.0/3.0等于三分之一。这两种方法都与真实值相差几百个四分之一,因此使用math.pi或1.0/3.0进行的任何计算也将被几百个四分之一的运算所抵消,包括取正弦值。

    如果你不喜欢这个近似算法 近似 那就不要用近似算法了。使用精确的算术。我以前在需要精确算术的时候用滑铁卢枫木,也许你应该买一份。

        4
  •  6
  •   David Thornley    14 年前

    这是浮点精度的结果。你可以得到一定数量的有效数字,任何不能精确表示的数字都是近似的。例如,π不是一个有理数,所以不可能得到精确的表示。因为你不能得到精确的π值,你不能得到包括π在内的精确的数的正弦波和余弦值(大多数时候你也不能得到正弦波和余弦的精确值)。

    最好的中间解释是 "What Every Computer Scientist Should Know About Floating-Point Arithmetic" . 如果你不想深入研究这个问题,只要记住浮点数通常是近似值,而浮点数的计算就像是在地面上移动一堆沙子:你用它们做的每一件事,都会损失一点沙子,并拾起一点灰尘。

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

        5
  •  1
  •   John Alexiou    12 年前

    我不认为错误是由于四舍五入造成的。可以做的就是定义 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°) 答案是准确的。

        6
  •  1
  •   TSchoening    6 年前

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

        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);
            }
        }