签名的夏亚 ATAN2 和 ATAN 近似值

Signed Char ATAN2 and ATAN approximations

本文关键字:ATAN 近似值 ATAN2 夏亚      更新时间:2023-10-16

基本上,我一直在尝试制作两个近似函数。在这两种情况下,我都输入了"x"和"y"组件(以处理那些讨厌的 n/0 和 0/0 条件(,并且需要获得有符号字符输出。在 ATAN2 的情况下,它应该提供 +/-PI 的范围,而在 ATAN 的情况下,范围应该是 +/- PI/2。

我昨天花了一整天的时间试图绕开它。在 excel 中玩弄后,根据近似值找到一个整体良好的算法:

    X * (PI/4 + 0.273 * (1 - |X|)) * 128/PI // Scale factor at end to switch to char format

我想出了以下代码:

signed char nabsSC(signed char x)
{
    if(x > 0)
        return -x;
    return x;
}
signed char signSC(signed char input, signed char ifZero = 0, signed char scaleFactor = 1)
{
    if(input > 0)
    {return scaleFactor;}
    else if(input < 0)
    {return -scaleFactor;}
    else
    {return ifZero;}
}
signed char divisionSC(signed char numerator, signed char denominator)
{
    if(denominator == 0)                // Error Condition
    {return 0;}
    else
    {return numerator/denominator;}
}
//#######################################################################################
signed char atan2SC(signed char y, signed char x)
{
    // @todo make clearer : the code was deduced through trial and error in excel with brute force... not the best reasoning in the world but hey ho
    if((x == y) && (x == 0))                            // Error Condition
    {return 0;}
                                    // Prepare for algorithm Choice
    const signed char X = abs(x);
    signed char Y = abs(y);
    if(Y > 2)
    {Y = (Y << 1) + 4;}
    const signed char alpha1 = 43;
    const signed char alpha2 = 11;
                                    // Make Choice
    if(X <= Y)                          // x/y Path
    {
        const signed char beta = 64;
        const signed char a = divisionSC(x,y);          // x/y
        const signed char A = nabsSC(a);                // -|x/y|
        const signed char temp = a * (alpha1 + alpha2 * A);     // (x/y) * (32 + ((0.273 * 128) / PI) * (1 - |x/y|)))
                                                        // Small angle approximation of ARCTAN(X)
        if(y < 0)                   // Determine Quadrant
        {return -(temp + beta);}
        else
        {return -(temp - beta);}
    }
    else                                // y/x Path
    {
        const signed char a = divisionSC(y,x);          // y/x
        const signed char A = nabsSC(a);                // -|y/x|
        const signed char temp = a * (alpha1 + alpha2 * A);     // (y/x) * (32 + ((0.273 * 128) / PI) * (1 - |y/x|)))
                                                        // Small angle approximation of ARCTAN(X)
        if(x < 0)                   // Determine Quadrant
        {
            Y = signSC(y, -127, 127);                       // Sign(y)*127, if undefined: use -127
            return temp + Y;
        }
        else
        {return temp;}
    }
}

令我绝望的是,该实现的错误高达180度,而且几乎无处不在。(在转换为有符号字符格式后,我将其与库中的 ATAN2F 进行了比较。

我从这个网站上得到了一般要点:http://geekshavefeelings.com/posts/fixed-point-atan2

谁能告诉我哪里出错了?以及我应该如何处理 ATAN 变体(应该更精确,因为它看起来超过一半的范围(,而没有所有这些疯狂。

我目前正在Windows上使用QT creator 4.8.1。这特定代码的最终平台最终将是一个没有FPU的微控制器,而ATAN功能将是使用的主要功能之一。因此,具有合理误差的效率(+/-2 度对于 ATAN2 和 +/-1 度对于 ATAN(。这些是现在的猜测,所以我可能会增加范围,但是,90度绝对是不可接受的!是游戏的目的。

提前感谢任何和所有的帮助!

编辑:澄清一下,ATAN2 和 ATAN 的输出输出为有符号字符值,但这两种类型的范围是不同的范围。

ATAN2的范围应为-128(-PI(至127(+PI - PI/128(。

ATAN 的范围为 -128 (-PI/2( 到 127(+PI/2 - PI/256(。

因此,两者的输出值可以被视为两种不同的数据类型。

很抱歉有任何困惑。

EDIT2:将隐式整数显式转换为有符号字符常量。

下面是一个大纲。 以下是其他信息。

  1. 结果角度(二元角度测量(数学上精确地将单位圆划分为 8 个楔形。 假设 -128 到 127 char ,对于 atan2SC(),每个八进制的结果是 33 个整数:0 到 32 + 偏移量。(0 到 32,而不是 0 到 31,因为四舍五入。 对于atan2SC(),结果是 0 到 64。 因此,只需专注于计算具有 x,y 个输入和 0 到 64 个结果的 1 个主辛烷值的结果。 atan2SC()atan2SC()都可以使用此帮助程序函数at2()。 对于atan2SC(),要找到中间角a,请使用a = at2(x,y)/2。对于atanSC(),请使用 a = at2(-128, y)

  2. a = divisionSC(x,y) 然后a * (43 + 11 * A)找到整数商会在除法中丢失太多信息。 需要用一个方程找到 atan2 近似值,该方程可能以 at2 = (a*y*y + b*y)/(c*x*x + d*x) 的形式使用x,y

  3. 最好像nabsSC()一样使用负绝对值。 整数的负范围满足或超过正范围。 例如 -128 到 -1 与 1 到 127。 调用 at2() 时使用负数和 0。


[编辑]

  1. 下面是具有简化的八位字节选择算法的代码。 它经过精心构造,以确保对x,y的任何否定都将导致SCHAR_MIN,SCHAR_MAX范围 - 假设 2 的完成。 所有八位字节都调用iat2(),这里是可以改进以提高精度的地方。 注意:iat2()被阻止除以 x==0,因为此时x不是 0。 根据舍入模式以及是否与atanSC()共享此帮助程序函数,将指示其详细信息。 建议一个 2 件的线性表是宽整数数学不可用,否则是一个线性(ay+b)/(cx+d)。 我可能会玩得更多。

  2. 精度与性能的权重对于OP的代码来说是至关重要的,但不足以让我得出最佳答案。 所以我在下面发布了一个测试驱动程序,用于评估 OP iat2()细节的精度。

  3. 存在 3 个陷阱。 1(当答案是+180度时,OP似乎想要-128 BAM。 但是atan2(-1, 0.0)想出了+pi。 这种标志反转可能是一个问题。 注意:atan2(-1, -0.0) --> -pi。裁判。 2( 当答案略小于 +180 度时,根据iat2()细节,整数 BAM 结果为 +128,趋向于 -128。 atan2()结果仅小于 +pi 或 +128 BAM。 此边缘条件需要在 OP 的最终代码中查看。 3((x=0,y=0(情况需要特殊处理。 八进制选择代码可以找到它。

  4. signed char atanSC(signed char x)的代码,如果需要快速,可以使用几个if()和一个64字节的查找表。 (假设 8 位有符号字符(。 同一表可用于 iat2()

.

#include <stdio.h>
#include <stdlib.h>
// -x > -y >= 0, so divide by 0 not possible
static signed char iat2(signed char y, signed char x) {
  // printf("x=%4d y=%4dn", x, y); fflush(stdout);
  return ((y*32+(x/2))/x)*2;  // 3.39 mxdiff
  // return ((y*64+(x/2))/x);    // 3.65 mxdiff
  // return (y*64)/x;            // 3.88 mxdiff
}
signed char iatan2sc(signed char y, signed char x) {
  // determine octant
  if (y >= 0) { // oct 0,1,2,3
    if (x >= 0) { // oct 0,1
      if (x > y) {
        return iat2(-y, -x)/2 + 0*32;
      } else {
        if (y == 0) return 0; // (x=0,y=0)
        return -iat2(-x, -y)/2 + 2*32;
      }
    } else { // oct 2,3
      // if (-x <= y) {
      if (x >= -y) {
        return iat2(x, -y)/2 + 2*32;
      } else {
        return -iat2(-y, x)/2 + 4*32;
      }
    }
  } else { // oct 4,5,6,7
    if (x < 0) { // oct 4,5
      // if (-x > -y) {
      if (x < y) {
        return iat2(y, x)/2 + -4*32;
      } else {
        return -iat2(x, y)/2 + -2*32;
      }
    } else { // oct 6,7
      // if (x <= -y) {
      if (-x >= y) {
        return iat2(-x, y)/2 + -2*32;
      } else {
        return -iat2(y, -x)/2 + -0*32;
      }
    }
  }
}
#include <math.h>
static void test_iatan2sc(signed char y, signed char x) {
  static int mn=INT_MAX;
  static int mx=INT_MIN;
  static double mxdiff = 0;
  signed char i = iatan2sc(y,x);
  static const double Pi = 3.1415926535897932384626433832795;
  double a = atan2(y ? y : -0.0, x) * 256/(2*Pi);
  if (i < mn) {
    mn = i;
    printf ("x=%4d,y=%4d  --> %4d   %f, mn %d mx %d mxdiff %fn", 
        x,y,i,a,mn,mx,mxdiff);
  }
  if (i > mx) {
    mx = i;
    printf ("x=%4d,y=%4d  --> %4d   %f, mn %d mx %d mxdiff %fn", 
        x,y,i,a,mn,mx,mxdiff);
  }
  double diff = fabs(i - a);
  if (diff > 128) diff = fabs(diff - 256);
  if (diff > mxdiff) {
    mxdiff = diff;
    printf ("x=%4d,y=%4d  --> %4d   %f, mn %d mx %d mxdiff %fn", 
        x,y,i,a,mn,mx,mxdiff);
  }
}

int main(void) {
  int x,y;
  int n = 127;
  for (y = -n-1; y <= n; y++) {
    for (x = -n-1; x <= n; x++) {
      test_iatan2sc(y,x);
    }
  }
  puts("Done");
  return 0;
}

顺便说一句:一个有趣的问题。