Как я должен заняться ARCTAN (и предотвратить деление на ноль) без удобства ARCTAN2 (n, m) быть доступным?
-
11-07-2019 - |
Вопрос
Я пытаюсь определить угол от точки (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 () может быть более точным для соотношений y / x между -1 и +1, чем для соотношений больше 1. (хотя хороший алгоритм atan () будет признать это и принять ответный ответ)
Другие советы
Если atan2 недоступен, вы должны проверить условие деления на ноль и все другие особые случаи в вашем коде. Легко как то. Запись в википедии на atan2 имеет все необходимые условия.
Если целевое оборудование поддерживает деление на ноль исключений для операций с плавающей запятой, у вас есть другая опция:
Установите низкоуровневый обработчик, который проверяет причину исключения и, если он является подразделением atan, устраняет проблему. Это сделает ваш atan2 быстрее, если исключения редки, но он требует низкоуровневой обработки и не переносим. Р>
Реализуйте стандарт 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)))
в противном случае для таких значений arctan сходится очень медленно
Определите свою версию arctan2
. Пример в C как макрос:
#define atan2(n,m) (m)==0 ? M_PI_2 : atan((n)/(m))
Конечно, вы можете разработать поиск квадранта в зависимости от признаков n
и m
.
Я считаю, что это правильная реализация atan2 с использованием atan (хотя и не обрабатывает бесконечности):
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);
}
}
}