Порівняння кутів та опрацювання різниці


27

Хочу порівняти кути і отримати уявлення про відстань між ними. Для цього додатка я працюю в градусах, але він також працював би для радіанів та градів. Проблема кутів полягає в тому, що вони залежать від модульної арифметики, тобто 0-360 градусів.

Скажіть, один кут - 15 градусів, а один - 45. Різниця - 30 градусів, а кут 45 градусів більший, ніж 15 градусів.

Але це виходить з ладу, коли у вас, скажімо, 345 градусів і 30 градусів. Хоча вони порівнюють належним чином, різниця між ними становить 315 градусів замість правильних 45 градусів.

Як я можу це вирішити? Я можу написати алгоритмічний код:

if(angle1 > angle2) delta_theta = 360 - angle2 - angle1;
else delta_theta = angle2 - angle1;

Але я вважаю за краще рішення, яке уникає порівняння / розгалуження, і повністю покладається на арифметику.


З цієї проблеми чи можна вважати, що задані кути знаходяться в межах [0,360] або (-нескінченний, нескінченний)? Наприклад, чи повинен алгоритм також порівнювати -130 градусів із 450?
egarcia

Припустимо, кути нормалізуються до цього діапазону.
Томас О

Відповіді:


29

Ось моя спрощена, без гілок, порівняння, без хв / макс. Версія:

angle = 180 - abs(abs(a1 - a2) - 180); 

Модуль видалено, оскільки входи досить обмежені (дякую Мартіну, що вказав на це).

Два абс, три віднімання.


Модуль вам не потрібен, вхідні значення обмежені діапазоном [0,360] (див. Коментар Томаса до оригінального подання). Досить акуратно.
Мартін Сойка

Ага, так, ти маєш рацію. У мене було менш суворий внесок, коли я спробував це.
JasonD

але що робити, якщо ви хочете зберегти знак різниці, щоб ви могли сказати, що було зліва?
Джейкоб Філіпс

9

Хоча вони порівнюють належним чином, різниця між ними становить 315 градусів замість правильних 45 градусів.

Чому ви вважаєте, що 315 невірно? В одному напрямку це 315 градусів, в іншому - 45. Ви хочете вибрати той, який є найменшим з двох можливих кутів, і це, здається, вимагає власного умови. Ви не можете вирішити це за допомогою арифметики обертання (тобто за допомогою оператора модуля), оскільки поступово збільшуючи один кут, кут між ними зростає, поки він не досягне 180, а потім починає зменшуватися.

Я думаю, що вам або доведеться перевірити обидва кути і вирішити, в якому напрямку потрібно виміряти, або обчислити обидва напрямки і вирішити, який результат ви хочете.


Вибачте, я повинен уточнити. Якщо ви робили це в зворотному порядку, 30 - 345 - -315, і негативний кут не має особливого сенсу. Я думаю, я шукаю найменший кут між ними. тобто на 45 градусів менше, ніж на 315.
Томас О

2
Але немає "зворотного" - у вас є 2 кути і 2 типи обертання, які ви можете виконати, щоб один збіг з іншим. Негативний кут має ідеальний сенс - зрештою, це лише міра обертання від довільної осі.
Kylotan

Якщо ви хочете найменший кут, то abs (a1% 180 - a2% 180) дасть вам цей кут. Однак він не скаже вам напрямок. Видалення абс дасть вам найменший кут "перехід від" a1 "до" a2
Chewy Gumball

2
@Chewy, так? Різниця між 180 і 0 не 0, а різниця між 181 і 0 не 1 ...
dash-tom-bang

1
@ dash-tom-bang Ви абсолютно праві. Я не знаю, про що я думав, але це було зовсім неправильно зараз, коли я дивлюся на це знову. Будь ласка, ігноруйте мій попередній коментар.
Chewy Gumball

4

Завжди є хитрість робити обидві гілки та дозволяти результату порівняння вибрати один:

delta_theta = (angle1 > angle2) * (360 - angle2 - angle1)
              + (angle2 > angle1) * (angle2 - angle1);

Я не знаю, як це зробити без порівнянь , але зазвичай галузь - це те, що робить код повільним і довгим, а не порівняння. Принаймні, на мою думку, це читабельніше, ніж відповідь Мартіна (будь-який хороший програміст C визнає це як еквівалент без галузей і побачить, що він робить), але також менш ефективний.

Але, як я вже сказав у своєму коментарі, алгоритми без розгалужень хороші для процесорів з глибокими трубопроводами і поганим прогнозуванням - мікроконтролер зазвичай має крихітний конвеєр, а настільний ПК зазвичай має хороший прогноз, тому, якщо ви не орієнтуєтесь на ігрову консоль, розгалужену версію мабуть, найкращий маршрут, якщо це зменшить кількість інструкцій.

Як завжди, профілювання - що може бути таким же простим, як підрахунок операцій для вашої системи - дасть вам справжню відповідь.


2

Припустимо, що істинні оцінки дорівнює -1, а помилкові - 0, а '~', '&' і '|' бітові не , і та або оператори відповідно, і ми працюємо з арифметикою двох доповнень:

temp1 := angle1 > angle2
/* most processors can do this without a jump; for example, under the x86 family,
   it's the result of CMP; SETLE; SUB .., 1 instructions */
temp2 := angle1 - angle2
temp1 := (temp1 & temp2) | (~temp1 & -temp2)
/* in x86 again: only SUB, AND, OR, NOT and NEG are used, no jumps
   at this point, we have the positive difference between the angles in temp1;
   we can now do the same trick again */
temp2 := temp1 > 180
temp2 := (temp2 & temp1) | (~temp2 & (360 - temp1))
/* the result is in temp2 now */

+1, тому що це розумно, але на мікроконтролері це, мабуть, набагато гірше, ніж версія розгалуження.

Трохи залежить від мікроконтролера, але, так, зазвичай цього не варто; (короткий) умовний стрибок, як правило, досить швидкий. Крім того, третій і п'ятий рядки можна переписати трохи швидше, використовуючи операцію xor (^), як це, але я залишив їх у поточній формі для наочності: temp1: = temp2 ^ ((temp2 ^ -temp2) & ~ temp1), temp2: = temp1 ^ ((temp1 ^ (360 - temp1)) & ~ temp2)
Мартін Сойка

1

Як що до цього?

min( (a1-a2+360)%360, (a2-a1+360)%360 )

Додавання 360 є там, щоб уникнути негативних відмінностей, оскільки модуль від’ємного числа повертає негативний результат. Тоді ви отримуєте менший з двох можливих результатів.

Є ще неявне рішення, але я не знаю, як цього уникнути. В основному ви порівнюєте два кути, обчислюючи різницю за годинниковою або проти годинникової стрілки, і, здається, ви явно хочете менший з цих двох відмінностей. Я не знаю, як досягти цього результату, не порівнюючи їх. Тобто, не використовуючи "abs", "min", "max" або якогось подібного оператора.


Існує кілька способів обчислити min, max та abs ints без інструкцій гілки, хоча оскільки це мікроконтролер, гілка, мабуть, найшвидший спосіб. graphics.stanford.edu/~seander/bithacks.html#IntegerAbs

1

Хоча у вашому питанні не було посилання на них, я буду працювати над припущенням, що ваше питання обчислення кута випливає з того, щоб хотіти знати мінімальний кут між двома векторами .

Цей розрахунок простий. Якщо A і B - це ваші вектори:

angle_between = acos( Dot( A.normalized, B.normalized ) )

Якщо у вас не було векторів і хотіли скористатися таким підходом, ви можете побудувати вектори одиниці довжини з урахуванням ваших кутів new Vector2( cos( angle ), sin ( angle ) ).


1
Процесор, над яким я працюю, - це невеликий мікроконтролер. Немає сенсу використовувати тригені функції для генерування вектора просто для того, щоб отримати різницю між кутами, кожен цикл дорогоцінний.
Томас О

1
На мікроконтролері я здивований, що не краще використовувати гілку, але в моїй відповіді не так багато арифметичних, якщо ви дійсно хочете уникнути розгалуження.
JasonD

Ну, гілка - це два цикли, а додавання / віднімання / тощо - один цикл, але розгалуження також займає додаткову пам'ять програми. Це не критично, але було б непогано.
Томас О

Я відчуваю, що ваша відповідь правильна, а моя - неправильна, але я не можу зрозуміти , чому це так. :)
Kylotan

1

В основному те саме, що відповідь JasonD, за винятком використання побітових операцій замість функції абсолютного значення.

Це за умови, що у вас є 16-бітні короткі цілі числа!

short angleBetween(short a,short b) {
    short x = a - b;
    short y = x >> 15;
    y = ((x + y) ^ y) - 180;
    return 180 - ((x + y) ^ y);
}


0

Оскільки ви дбаєте лише про усунення гілок та "складних" операцій поза межі арифметики, я рекомендую це:

min(abs(angle1 - angle2), abs(angle2 - angle1))

Ви все ще потребуєте absтам, незважаючи на те, що всі кути позитивні. Інакше завжди буде обраний найнегативніший результат (і завжди буде одна річ негативна відповідь на позитивний, унікальний a і b при порівнянні ab і ba).

Примітка. Це не збереже напрям між кутом1 та кутом2. Іноді це потрібно для цілей ШІ.

Це схоже на відповідь CeeJay, але усуває всі модулі. Я не знаю, яка вартість циклу abs, але я думаю, що це 1 або 2. Важко сказати, яка вартість minтакож. Може 3? Отже, поряд з 1 циклом на віднімання, цей рядок повинен мати вартість десь від 4 до 9.


0

Отримайте менший відносний кут в підписаному вигляді (+/-), з точки зору мати в стороні потреби :

  • не більше 180 градусів | PI радіани
  • -написаний, якщо проти годинникової стрілки
  • + підписано, якщо за годинниковою стрілкою

Градуси

PITAU = 360 + 180 # for readablility
signed_diff = ( want - have + PITAU ) % 360 - 180

Радіан

PI = 3.14; TAU = 2*PI; PITAU = PI + TAU;
signed_diff = ( want - have + PITAU ) % TAU - PI

Обґрунтування

Я натрапив на цю нитку після того, як я зрозумів це, шукаючи рішення, яке уникає модуля; поки що я не знайшов жодного . Це рішення призначене для збереження знаку перспективи, як @ jacob-phillips попросив цей коментар . Є більш дешеві рішення, якщо вам просто потрібен найкоротший неподписаний кут.


0

Це старе питання, але я зіткнувся з одним і тим же випадком - довелося підписати кутову різницю і бажано без гілок і важкої математики. Ось що я закінчив:

int d = (a - b) + 180 + N * 360; // N = 1, 2 or more.
int r = (d / 360) * 360;
return (d - r) - 180;

Обмеження полягає в тому, що "b" не повинен мати більше обертів "N" порівняно з "a". Якщо ви не можете забезпечити це та можете дозволити додаткові операції, використовуйте це як перший рядок:

int d = ((a % 360) - (b % 360)) + 540;

Я отримав ідею з 13-го коментаря до цього допису: http://blog.lexique-du-net.com/index.php?post/Calculate-the-real-difference-between-two-angles-keeping-the- знак


-1

я думаю, я міг би сказати

angle1=angle1%360;
angle2=angle2%360;
var distance = Math.abs(angle1-angle2);
//edited
if(distance>180)
  distance=360-distance;

Звичайно, враховуючи кут вимірювання в градусах.


1
Я не вірю, що це вирішує питання в питанні. 345% 360 == 345, а абс (345-30) все ще 315.
Грегорі Ейвери-Вейр

@Gregory: добре !, вибачте за помилку. Я редагую відповідь, перевірте цю нову. :)
Вішну

1
До речі, кут1 = кут1% 360; кут2 = кут2% 360; var відстань = Math.abs (angle1-angle2); те саме, що var відстань = Math.abs (angle1-angle2)% 360 - просто повільніше.
Мартін Сойка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.