【问题标题】:Accuracy of Math.Sin() and Math.Cos() in C#C# 中 Math.Sin() 和 Math.Cos() 的准确性
【发布时间】:2010-07-14 19:25:54
【问题描述】:

我对 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# 编写了一些更好的三角函数表示,以便与世界分享。 CLR 是否调用了一些实现 CORDIC 或类似的标准 C 数学库?链接:wikipedia CORDIC

【问题讨论】:

  • 您认为将 pi 表示为 double 的准确度如何?
  • 如果你想要符号数学,就做符号数学。如果你使用浮点类型,你会得到有限的精度。
  • -1 表示没有“做作业”,也表示认为System.Math 是 C# 的一部分(提示:它是 .NET Framework 的一部分)。
  • 如果您以英里为单位测量地球的位置,如上所示,Math.Sin(Math.PI) 将给出一个相差十分之七英寸的位置。您正在开发什么应用程序需要如此精确?!
  • @jalexiou:如果您正在处理涉及微分方程和数值积分的动力学问题,所有这些问题都是浮点数,那么 pi 值的几万亿分之一的不准确性可能是您的问题中最少的问题。微分方程和求积的数值逼近算法存在许多众所周知的误差项增长过大问题。如果您想继续追求数值逼近解决方案,请选择一本关于数值方法的优秀本科教材。如果你想用精确的算术来做数学,找mathematica或maple之类的工具。

标签: c# clr trigonometry


【解决方案1】:

这与三角函数的准确性无关,而更多地与 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();
}

【讨论】:

    【解决方案2】:

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

    Console.WriteLine(1.0 / 3.0);
    

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

    也许现在您明白问题所在了。 Math.Pi 不等于 pi 大于 1.0 / 3.0 等于三分之一。它们都与真实值相差几百万亿分之一,因此您使用 Math.Pi 或 1.0/3.0 执行的任何计算也将相差几百万亿分之一,包括取正弦。

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

    【讨论】:

    • 出于好奇,如果想用 C# 计算精确的算术,有可能吗?只是不知道可以用什么来做到这一点?甚至小数都不会削减它,对吧?
    • @Joan:是的,我们可以很容易地处理整数,甚至是有理数(只需将分子/分母存储为大整数),并将规则放入我们的库中以获取我们想要的任何特定实数: pi, e, square-roots 等。然而,对任何可想象的实数进行任意精度算术的库是不可能的;即使假设你有某种方式来存储它们(比如作为一个公式,它会给我们一个任意数字的数字),在计算上甚至不可能比较两个任意实数是否相等!!
    • @BlueRaja:确实。通常你在这种情况下所做的就是象征性地操纵算术量。你有一个 pi 的符号和一个 e 的符号,就像你有一个 1、2、3 的符号一样,然后你编码所有的算术和三角恒等式,比如 pi 的正弦为零,等等。符号数学很难。
    【解决方案3】:

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

    最好的中间解释是"What Every Computer Scientist Should Know About Floating-Point Arithmetic"。如果你不想深入,请记住浮点数通常是近似值,浮点计算就像在地面上移动一堆沙子:你对它们所做的一切,你都会失去一点沙子,捡起一点泥土。

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

    【讨论】:

    • 我知道 PI 是否由于 IEEE-754 算法而没有准确定义,我希望我可以用 C# 驱动一个符号代数系统,但我现在不能。
    【解决方案4】:

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

    一些流行的可用:

    【讨论】:

      【解决方案5】:

      我拒绝错误是由于四舍五入造成的想法。可以做的是定义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°),答案是准确的。

      【讨论】:

      • 关于哪些角度具有精确触发的相关帖子。值math.stackexchange.com/q/176889/3301
      • 如果唯一的问题是 OP 的特殊情况,不妨将它们写成 if-else 语句,而不是采用这样的解决方案。这些方法可以在直接调用时起作用,例如零,但不是一个近似值。如果例如你用前一个操作的近似结果调用 Sin,它不会起作用(例如,因为 x 非常小,但不完全为零——即使它“应该”是)。这里真正的问题是使用 double 和期望得到准确的结果,这是世界上没有任何算法或公式可以解决的。
      【解决方案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);
              }
          }
      

      【讨论】:

      • 尝试Sin(1e16*Math.PI) 哪个C# 产生-0.375175399369948 而正确的0。事实上,这与您在MATLAB、Excel 和任何其他使用x87 的数值库中获得的结果相同用于触发的协处理器。从最初的帖子开始,我意识到问题不是C#,而是所有现代 PC 都使用的底层 IEEE 数学平台。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-25
      • 2021-04-03
      • 2017-02-17
      相关资源
      最近更新 更多