Najmniejsza różnica między 2 kątami

137

Biorąc pod uwagę 2 kąty w zakresie -PI -> PI wokół współrzędnej, jaka jest wartość najmniejszego z dwóch kątów między nimi?

Biorąc pod uwagę, że różnica między PI i -PI nie wynosi 2 PI, ale zero.

Przykład:

Wyobraź sobie okrąg, z 2 liniami wychodzącymi ze środka, między tymi liniami są 2 kąty, kąt, jaki tworzą wewnątrz, czyli mniejszy kąt , i kąt, który tworzą na zewnątrz, czyli większy kąt. Oba kąty po zsumowaniu tworzą pełne koło. Biorąc pod uwagę, że każdy kąt może mieścić się w pewnym zakresie, jaka jest mniejsza wartość kątów, biorąc pod uwagę najazd

Tom J Nowell
źródło
2
Przeczytałem 3 razy, zanim zrozumiałem, co masz na myśli. Proszę dodać przykład lub wyjaśnić lepiej ...
Kobi
Wyobraź sobie okrąg, z 2 liniami wychodzącymi ze środka, między tymi liniami są 2 kąty, kąt, jaki tworzą wewnątrz, czyli mniejszy kąt, i kąt, który tworzą na zewnątrz, czyli większy kąt. Oba kąty po zsumowaniu tworzą pełne koło. Biorąc pod uwagę, że każdy kąt może mieścić się w pewnym zakresie, jaka jest mniejsza wartość kątów, biorąc pod uwagę kumulację
Tom J Nowell,
2
@JimG. to nie jest to samo pytanie, w tym pytaniu kąt P1 użyty w drugim pytaniu byłby błędną odpowiedzią, byłby to inny, mniejszy kąt. Nie ma również gwarancji, że kąt jest zgodny z osią poziomą
Tom J Nowell,

Odpowiedzi:

193

Daje to oznaczony kąt dla dowolnych kątów:

a = targetA - sourceA
a = (a + 180) % 360 - 180

Uwaga w wielu językach modulooperacja zwraca wartość z tym samym znakiem co dywidenda (np. C, C ++, C #, JavaScript, pełna lista tutaj ). Wymaga to niestandardowej modfunkcji, takiej jak:

mod = (a, n) -> a - floor(a/n) * n

Lub tak:

mod = (a, n) -> (a % n + n) % n

Jeśli kąty mieszczą się w przedziale [-180, 180], to również działa:

a = targetA - sourceA
a += (a>180) ? -360 : (a<-180) ? 360 : 0

W bardziej szczegółowy sposób:

a = targetA - sourceA
a -= 360 if a > 180
a += 360 if a < -180
bennedich
źródło
Prostsze i ma więcej sensu czytać na głos, choć w praktyce to samo, pierwsza bti określa kąt, druga część upewnia się, że zawsze jest mniejszy z 2 możliwych kątów
Tom J Nowell
1
chociaż ktoś mógłby chcieć zrobić% 360, np. gdybym miał kąt 0 i kąt docelowy 721, prawidłowa odpowiedź to 1, odpowiedź udzielona przez powyższe to 361
Tom J Nowell
1
Bardziej zwięzły, choć potencjalnie droższy, odpowiednik drugiego stwierdzenia tego ostatniego podejścia jest a -= 360*sgn(a)*(abs(a) > 180). (Pomyśl o tym, jeśli masz bezgałęziowe implementacje sgni abs, wtedy ta cecha może faktycznie zacząć rekompensować potrzebę dwóch mnożeń.)
mmirate
1
Przykład „Podpisany kąt dla dowolnego kąta” wydaje się działać w większości scenariuszy, z jednym wyjątkiem. W scenariuszu double targetA = 2; double sourceA = 359;`` a '' będzie równe -357,0 zamiast 3,0
Stevoisiak
3
W C ++ możesz użyć std :: fmod (a, 360) lub fmod (a, 360), aby użyć zmiennoprzecinkowego modulo.
Joeppie
145

x to kąt docelowy. y jest źródłem lub kątem początkowym:

atan2(sin(x-y), cos(x-y))

Zwraca podpisany kąt delta. Zauważ, że w zależności od twojego API kolejność parametrów dla funkcji atan2 () może być różna.

Peter B.
źródło
13
x-ypodaje różnicę kąta, ale może wykraczać poza pożądane granice. Pomyśl o tym kącie definiującym punkt na okręgu jednostkowym. Współrzędne tego punktu to (cos(x-y), sin(x-y)). atan2zwraca kąt dla tego punktu (który jest równoważny x-y), z wyjątkiem tego, że jego zakres to [-PI, PI].
Max
3
To przechodzi zestaw testów gist.github.com/bradphelan/7fe21ad8ebfcb43696b8
bradgonesurfing
2
jedno wierszowe proste rozwiązanie i rozwiązane za mnie (nie wybrana odpowiedź;)). ale odwrotnie tan jest kosztownym procesem.
Mohan Kumar
2
Dla mnie najbardziej eleganckie rozwiązanie. Szkoda, że ​​to może być kosztowne obliczeniowo.
focs
Dla mnie też najbardziej eleganckie rozwiązanie! Idealnie rozwiązałem mój problem (chciałem mieć wzór, który daje mi oznaczony kąt skrętu, który jest mniejszy z dwóch możliwych kierunków / kątów skrętu).
Jürgen Brauer
41

Jeśli twoje dwa kąty są x i y, to jeden z kątów między nimi to abs (x - y). Drugi kąt to (2 * PI) - abs (x - y). Zatem wartość najmniejszego z dwóch kątów to:

min((2 * PI) - abs(x - y), abs(x - y))

Daje to bezwzględną wartość kąta i zakłada, że ​​dane wejściowe są znormalizowane (tj. W zakresie [0, 2π) ).

Jeśli chcesz zachować znak (tj: kierunek) kąta, a także zaakceptować kąty spoza zakresu [0, 2π), możesz uogólnić powyższe. Oto kod Pythona dla wersji uogólnionej:

PI = math.pi
TAU = 2*PI
def smallestSignedAngleBetween(x, y):
    a = (x - y) % TAU
    b = (y - x) % TAU
    return -a if a < b else b

Zauważ, że %operator nie zachowuje się tak samo we wszystkich językach, szczególnie gdy używane są wartości ujemne, więc jeśli przenoszenie może być konieczne pewne korekty znaków.

Laurence Gonsalves
źródło
1
@bradgonesurfing To prawda, ale aby być uczciwym, testy sprawdzały pod kątem rzeczy, które nie zostały określone w pierwotnym pytaniu, w szczególności nieznormalizowanych danych wejściowych i zachowania znaków. Druga wersja zredagowanej odpowiedzi powinna zdać twoje testy.
Laurence Gonsalves
Druga wersja też u mnie nie działa. Na przykład spróbuj 350 i 0. Powinien zwrócić -10, ale zwraca -350
kjyv
@kjyv Nie mogę odtworzyć opisanego przez Ciebie zachowania. Czy możesz wysłać dokładny kod?
Laurence Gonsalves,
Ach, przepraszam. Przetestowałem dokładnie twoją wersję z rad i stopniami w Pythonie i działała dobrze. Więc to musiał być błąd w moim tłumaczeniu na C # (już go nie mam).
kjyv
2
Zwróć uwagę, że od Pythona 3 możesz używać tau natywnie! Po prostu napisz from math import tau.
mhartl
8

Podejmuję wyzwanie udzielenia podpisanej odpowiedzi:

def f(x,y):
  import math
  return min(y-x, y-x+2*math.pi, y-x-2*math.pi, key=abs)
David Jones
źródło
1
Ach ... Nawiasem mówiąc, odpowiedzią jest funkcja Pythona. Przepraszam, byłem przez chwilę w trybie Python. Mam nadzieję, że wszystko w porządku.
David Jones,
Podłączę nową formułę do mojego kodu na górze i zobaczę, co się z nim stanie! (dziękuję ^ _ ^)
Tom J Nowell
1
Jestem prawie pewien, że odpowiedź PeterB jest również poprawna. I złośliwie hackerski. :)
David Jones
4
Ale ten nie zawiera żadnych funkcji trygonometrycznych :)
nornagon
Jaka jest równoważna formuła dla języka Java? jeśli kąty są w stopniach.?
Soley
6

Dla użytkowników UnityEngine prostym sposobem jest użycie Mathf.DeltaAngle .

Josh
źródło
1
Nie ma podpisanego wyjścia
kjyv
2

Wydajny kod w C ++, który działa dla każdego kąta i obu: radianów i stopni, to:

inline double getAbsoluteDiff2Angles(const double x, const double y, const double c)
{
    // c can be PI (for radians) or 180.0 (for degrees);
    return c - fabs(fmod(fabs(x - y), 2*c) - c);
}
Adriel Jr
źródło
-1

Nie ma potrzeby obliczania funkcji trygonometrycznych. Prosty kod w języku C to:

#include <math.h>
#define PIV2 M_PI+M_PI
#define C360 360.0000000000000000000
double difangrad(double x, double y)
{
double arg;

arg = fmod(y-x, PIV2);
if (arg < 0 )  arg  = arg + PIV2;
if (arg > M_PI) arg  = arg - PIV2;

return (-arg);
}
double difangdeg(double x, double y)
{
double arg;
arg = fmod(y-x, C360);
if (arg < 0 )  arg  = arg + C360;
if (arg > 180) arg  = arg - C360;
return (-arg);
}

niech dif = a - b, w radianach

dif = difangrad(a,b);

niech dif = a - b, w stopniach

dif = difangdeg(a,b);

difangdeg(180.000000 , -180.000000) = 0.000000
difangdeg(-180.000000 , 180.000000) = -0.000000
difangdeg(359.000000 , 1.000000) = -2.000000
difangdeg(1.000000 , 359.000000) = 2.000000

Bez grzechu, bez cos, bez opalenizny .... tylko geometria !!!!

Uli Gue
źródło
7
Pluskwa! Ponieważ # definiujesz PIV2 jako „M_PI + M_PI”, a nie „(M_PI + M_PI)”, linia arg = arg - PIV2;rozwija się do arg = arg - M_PI + M_PI, więc nic nie robi.
kanton 7