三角函数计算的源代码

时间:2014-05-26 19:01:36

标签: c++ trigonometry numerical-stability

对于需要确定性且在不同平台(编译器)上提供相同结果的程序,不能使用内置三角函数,因为计算它的算法在不同系统上是不同的。经过测试,结果值不同。

(编辑:结果需要与在最后一位完全相同,因为它在所有客户端上运行的游戏模拟中使用。这些客户端需要使模拟的状态完全相同任何小错误都可能导致越来越大的错误,并且游戏状态的crc也用作同步检查。

所以我提出的唯一解决方案是使用我们自己的自定义代码来计算这些值,问题是,(令人惊讶的是)很难找到所有设置的任何易于使用的源代码三角函数。

这是我对sin函数的代码(https://codereview.stackexchange.com/questions/5211/sine-function-in-c-c)的修改。它在所有平台上都是确定性的,并且该值与标准sin的值几乎相同(均经过测试)。

#define M_1_2_PI 0.159154943091895335769 // 1 / (2 * pi)

double Math::sin(double x)
{
  // Normalize the x to be in [-pi, pi]
  x += M_PI;
  x *= M_1_2_PI;
  double notUsed;
  x = modf(modf(x, &notUsed) + 1, &notUsed);
  x *= M_PI * 2;
  x -= M_PI;

  // the algorithm works for [-pi/2, pi/2], so we change the values of x, to fit in the interval,
  // while having the same value of sin(x)
  if (x < -M_PI_2)
    x = -M_PI - x;
  else if (x > M_PI_2)
    x = M_PI - x;
  // useful to pre-calculate
  double x2 = x*x;
  double x4 = x2*x2;

  // Calculate the terms
  // As long as abs(x) < sqrt(6), which is 2.45, all terms will be positive.
  // Values outside this range should be reduced to [-pi/2, pi/2] anyway for accuracy.
  // Some care has to be given to the factorials.
  // They can be pre-calculated by the compiler,
  // but the value for the higher ones will exceed the storage capacity of int.
  // so force the compiler to use unsigned long longs (if available) or doubles.
  double t1 = x * (1.0 - x2 / (2*3));
  double x5 = x * x4;
  double t2 = x5 * (1.0 - x2 / (6*7)) / (1.0* 2*3*4*5);
  double x9 = x5 * x4;
  double t3 = x9 * (1.0 - x2 / (10*11)) / (1.0* 2*3*4*5*6*7*8*9);
  double x13 = x9 * x4;
  double t4 = x13 * (1.0 - x2 / (14*15)) / (1.0* 2*3*4*5*6*7*8*9*10*11*12*13);
  // add some more if your accuracy requires them.
  // But remember that x is smaller than 2, and the factorial grows very fast
  // so I doubt that 2^17 / 17! will add anything.
  // Even t4 might already be too small to matter when compared with t1.

  // Sum backwards
  double result = t4;
  result += t3;
  result += t2;
  result += t1;

  return result;
}

但我找不到任何适合其他功能的东西,比如asin,atan,tan(除了sin / cos)等。

这些功能并不像标准功能那样精确,但至少8个数字会很好。

4 个答案:

答案 0 :(得分:4)

  

“经测试,结果值不同。”

差异有多么不同?您声称需要8个重要(十进制?)数字的协议。我不相信你在任何符合ISO/IEC 10967-3:2006§5.3.2的实现中发现的数量都少于此。

你是否理解十亿分之一的三角误差是多么微不足道?在地球轨道大小的圆上不到3公里。除非您计划前往火星,并使用不合标准的实施,否则您声称的“不同”并不重要。

在回复评论时添加了

What Every Programmer Should Know About Floating-Point Arithmetic。阅读。严重。

因为你声称:

  1. 精度不如位相等的重要性
  2. 您只需要8位有效数字
  3. 然后你应该将你的值截断为8位有效数字。

答案 1 :(得分:1)

你可以使用泰勒系列(实际上它似乎是你正在使用的,也许不知道)

查看维基百科(或其他任何地方): https://en.wikipedia.org/wiki/Taylor_series

这里有最常用功能的列表(exp,log,cos,sin等...)https://en.wikipedia.org/wiki/Taylor_series#List_of_Maclaurin_series_of_some_common_functions 但是通过一些数学知识,你可以找到/计算所有的东西(显然不是除了......之外的一切)。

一些例子(还有很多其他例子)

Taylor1

注意:

  1. 您添加的术语越多,您的精确度就越高。
  2. 我不认为这是计算你需要的东西的有效方法,但它是一个非常“简单”的方法(我的意思)
  3. 如果您决定使用
  4. factorial(n)函数可能非常有用

    我希望它会有所帮助。

答案 2 :(得分:1)

我想最简单的方法是选择一个实现所需数学函数的自由运行库:

只需使用他们的实现。请注意,上面列出的是公共域或BSD许可或其他一些自由许可。如果您使用该代码,请务必遵守许可。

答案 3 :(得分:0)

我建议使用查找表和线性/双三次插值。

通过这种方式,您可以精确控制每个点的值,而不必进行大量的乘法运算。

无论如何,对于sin / cos函数的Taylor展开很糟糕

spring rts在这种类型的异步错误中奋斗了很久:尝试在他们的论坛上发帖,没有多少老开发者留下,但那些仍然应该记住问题和修复的人。

在这个帖子http://springrts.com/phpbb/viewtopic.php?f=1&t=8265中他们专门讨论了libm确定性(但是不同的os可能有不同的libc和微妙的优化差异,所以你需要采用这种方法并抛出库)