签名Char ATAN2和ATAN近似值

时间:2014-10-05 08:51:43

标签: c++ c atan2

基本上,我一直在尝试制作两个近似函数。在这两种情况下,我输入“x”和“y”组件(以处理那些令人讨厌的n / 0和0/0条件),并且需要获得一个Signed Char输出。在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功能将成为使用的主要功能之一。因此,具有合理误差的效率(对于ATAN2为+/- 2度,对于ATAN为+/- 1度。这些是现在的猜测,所以我可能会增加范围,但是,90度绝对是不可接受的!)是目标游戏。

提前感谢您提供的任何帮助!

编辑: 只是为了澄清,ATAN2和ATAN的输出输出到有符号的char值,但两种类型的范围是不同的范围。

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

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

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

对不起有任何困惑。

EDIT2:将隐式int数明确转换为signed char常量。

1 个答案:

答案 0 :(得分:0)

概述如下。以下是其他信息。

  1. 结果角度(二进制角度测量)完全在数学上将单位圆分为8个楔形。假设-128到127 char,对于atan2SC(),每个八分圆的结果是33个整数:0到32 +一个偏移量。 (由于四舍五入,为0到32,而不是0到31.)对于atan2SC(),结果为0到64.因此,只需关注计算1个主要八分圆的结果,其中x,y个输入为0到0 64结果。 atan2SC()atan2SC()都可以使用此辅助函数at2()。对于atan2SC(),要查找中间角度a,请使用a = at2(x,y)/2。对于atanSC(),请使用a = at2(-128, y)

  2. 使用a = divisionSC(x,y)然后a * (43 + 11 * A)查找整数商会在分区中丢失太多信息。需要使用x,y形式使用at2 = (a*y*y + b*y)/(c*x*x + d*x)的等式找到atan2近似值。

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


  4. [编辑]

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

    2. 对于OP的代码,精确度与性能的关系是一个至关重要的因素,但不能很好地传递给我以获得最佳答案。所以我在下面发布了一个测试驱动程序,用于评估iat2() OP出现的详细信息的准确性。

    3. 存在3个陷阱。 1)当答案是+180度时,OP似乎想要-128 BAM。但是atan2(-1, 0.0)提出了+ pi。这种标志逆转可能是一个问题。注意:atan2(-1, -0.0) - &gt; -pi。 Ref。 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()

    5. 中使用

      #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=%4d\n", 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 %f\n", 
              x,y,i,a,mn,mx,mxdiff);
        }
        if (i > mx) {
          mx = i;
          printf ("x=%4d,y=%4d  --> %4d   %f, mn %d mx %d mxdiff %f\n", 
              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 %f\n", 
              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;
      }
      
      BTW:一个有趣的问题。