我试图确定从一点(n,m)
到的角度(0,0)
。在没有arctan2
可用的情况下,我m
遇到了可能为 0 的问题,这导致可能被零除。
解决这个问题的优雅、正确的解决方案是什么?
我试图确定从一点(n,m)
到的角度(0,0)
。在没有arctan2
可用的情况下,我m
遇到了可能为 0 的问题,这导致可能被零除。
解决这个问题的优雅、正确的解决方案是什么?
不要使用传统的象限,使用由线 y = +/- x 定义的分支点,并使用类似 CORDIC 的算法的第一步两步(例如,将坐标旋转已知角度并跟踪您'已经旋转):
function atan2_substitute(x,y)
{
double angle = 0;
if (x < y)
{ angle = M_PI; x = -x; y = -y; }
// this guarantees that the angle is between -135 and +45 degrees
if (x < -y)
{
angle -= M_PI/2; tmp = x; x = -y; y = tmp;
}
// this guarantees that the angle is between -45 and +45
angle += atan(y/x);
if (angle > M_PI)
angle -= 2*M_PI;
// fails at 0,0; otherwise is accurate over the entire plane
}
这样做的原因是 atan() 对于 -1 和 +1 之间的比率 y/x 可能比对于大于 1 的比率更准确。(尽管一个好的 atan() 算法会认识到这一点并且取倒数)
如果 atan2 不可用,您必须检查代码中的除以零条件和所有其他特殊情况。就这么简单。atan2 上的维基百科条目具有您需要的所有条件。
如果您的目标硬件支持浮点运算的除零异常,您还有另一个选择:
安装一个低级处理程序来检查异常原因,如果它碰巧是一个 atan 部门,请修复问题。如果异常很少,这将使您的 atan2 更快,但它需要低级别的修补并且不可移植。
使用 Taylor 级数实现标准arctan(n, m)
,并在计算 arctan 之前执行以下操作:
if (m == 0) {
if (n < 0) return Pi;
return 0;
}
还有一些技巧:
1)如果|m| < |n|
,交换m, n
然后计算arctan。最后减去结果Pi/2
2) 如果|m|
接近|n|
,则使用半角公式 计算半角的反正切
arctan(x) = 2*arctan(x/(1+sqrt(1+x*x)))
,否则,对于这样的值,反正切收敛非常慢
定义您的arctan2
. C 中作为宏的示例:
#define atan2(n,m) (m)==0 ? M_PI_2 : atan((n)/(m))
当然,您可以详细说明根据 和 的符号找到n
象限m
。
我相信这是使用 atan 的 atan2 的正确实现(虽然不处理无穷大):
float my_atan2(float y, float x)
{
if(x == 0) // might also want to use fabs(x) < 1e-6 or something like that
{
if(y > 0)
return M_PI_2;
else
return -M_PI_2;
}
else if(x > 0)
{
return atan(y/x);
}
else
{
// x < 0
if(y > 0)
return M_PI + atan(y/x);
else
return -M_PI + atan(y/x);
}
}
测试线束:
int main()
{
for(int i = -360; i <= 360; i++)
{
float x = cos(i / 180.0 * M_PI);
float y = sin(i / 180.0 * M_PI);
float good = atan2(y, x);
float mine = my_atan2(y, x);
if(fabs(good - mine) > 1e-6)
{
printf("%d %f %f %f %f\n", i, x, y, good, mine);
}
}
}