La menor diferencia entre 2 ángulos

137

Dados 2 ángulos en el rango -PI -> PI alrededor de una coordenada, ¿cuál es el valor del menor de los 2 ángulos entre ellos?

Teniendo en cuenta que la diferencia entre PI y -PI no es 2 PI sino cero.

Ejemplo:

Imagine un círculo, con 2 líneas que salen del centro, hay 2 ángulos entre esas líneas, el ángulo que forman en el interior, también conocido como el ángulo más pequeño , y el ángulo que forman en el exterior, también conocido como el ángulo más grande. Ambos ángulos cuando se suman forman un círculo completo. Dado que cada ángulo puede caber dentro de un cierto rango, ¿cuál es el valor de los ángulos más pequeños, teniendo en cuenta el rollover?

Tom J Nowell
fuente
2
Leí 3 veces antes de entender lo que querías decir. Agregue un ejemplo o explique mejor ...
Kobi
Imagine un círculo, con 2 líneas que salen del centro, hay 2 ángulos entre esas líneas, el ángulo que forman en el interior, también conocido como el ángulo más pequeño, y el ángulo que forman en el exterior, también conocido como el ángulo más grande. Ambos ángulos cuando se suman forman un círculo completo. Dado que cada ángulo puede ajustarse dentro de un cierto rango, lo que es el valor de los ángulos más pequeños, teniendo en cuenta el vuelco
Tom J Nowell
2
@JimG. esta no es la misma pregunta, en esta pregunta el ángulo P1 usado en la otra pregunta sería la respuesta incorrecta, sería el otro ángulo más pequeño. Además, no hay garantía de que el ángulo esté con el eje horizontal
Tom J Nowell

Respuestas:

193

Esto proporciona un ángulo con signo para cualquier ángulo:

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

Tenga cuidado con que en muchos idiomas la modulooperación devuelve un valor con el mismo signo que el dividendo (como C, C ++, C #, JavaScript, lista completa aquí ). Esto requiere una modfunción personalizada como esta:

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

Más o menos:

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

Si los ángulos están dentro de [-180, 180] esto también funciona:

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

De una manera más detallada:

a = targetA - sourceA
a -= 360 if a > 180
a += 360 if a < -180
bennedich
fuente
Es más simple y tiene más sentido leer en voz alta, aunque efectivamente es lo mismo, primero bti calcula el ángulo, la segunda parte se asegura de que siempre sea el menor de los 2 ángulos posibles
Tom J Nowell
1
aunque es posible que desee hacer un% 360, por ejemplo, si tuviera el ángulo 0 y el ángulo objetivo 721, la respuesta correcta sería 1, la respuesta dada por lo anterior sería 361
Tom J Nowell
1
Un equivalente más conciso, aunque potencialmente más costoso, de la segunda declaración del último enfoque es a -= 360*sgn(a)*(abs(a) > 180). (Ahora que lo pienso, si tiene implementaciones sin ramificaciones de sgny abs, entonces esa característica podría comenzar a compensar la necesidad de dos multiplicaciones.)
mmirate
1
El ejemplo de "ángulo firmado para cualquier ángulo" parece funcionar en la mayoría de los escenarios, con una excepción. En el escenario double targetA = 2; double sourceA = 359;'a' será igual a -357.0 en lugar de 3.0
Stevoisiak
3
En C ++ puede usar std :: fmod (a, 360) o fmod (a, 360) para usar el módulo de coma flotante.
Joeppie
145

x es el ángulo objetivo. y es la fuente o ángulo de inicio:

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

Devuelve el ángulo delta firmado. Tenga en cuenta que, según su API, el orden de los parámetros para la función atan2 () puede ser diferente.

Peter B
fuente
13
x-yle da la diferencia en ángulo, pero puede estar fuera de los límites deseados. Piense en este ángulo que define un punto en el círculo unitario. Las coordenadas de ese punto son (cos(x-y), sin(x-y)). atan2devuelve el ángulo para ese punto (que es equivalente a x-y) excepto que su rango es [-PI, PI].
Max
3
Esto pasa la suite de pruebas gist.github.com/bradphelan/7fe21ad8ebfcb43696b8
bradgonesurfing
2
una solución simple de una línea y resuelta para mí (no la respuesta seleccionada;)). pero tan inverso es un proceso costoso.
Mohan Kumar
2
Para mí, la solución más elegante. Es una pena que sea computacionalmente costoso.
focs
¡Para mí la solución más elegante también! Resolvió mi problema perfectamente (quería tener una fórmula que me diera el ángulo de giro con signo , que es el más pequeño de las dos direcciones / ángulos de giro posibles).
Jürgen Brauer
41

Si sus dos ángulos son x e y, entonces uno de los ángulos entre ellos es abs (x - y). El otro ángulo es (2 * PI) - abs (x - y). Entonces el valor del más pequeño de los 2 ángulos es:

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

Esto le da el valor absoluto del ángulo y supone que las entradas están normalizadas (es decir, dentro del rango [0, 2π)).

Si desea conservar el signo (es decir, la dirección) del ángulo y también acepta ángulos fuera del rango [0, 2π), puede generalizar lo anterior. Aquí está el código de Python para la versión generalizada:

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

Tenga en cuenta que el %operador no se comporta de la misma manera en todos los idiomas, especialmente cuando hay valores negativos, por lo tanto, si se transfieren algunos signos, puede ser necesario.

Laurence Gonsalves
fuente
1
@bradgonesurfing Eso es / era cierto, pero para ser justos, sus pruebas verificaron cosas que no se especificaron en la pregunta original, específicamente entradas no normalizadas y preservación de signos. La segunda versión en la respuesta editada debe pasar sus pruebas.
Laurence Gonsalves
La segunda versión tampoco me funciona. Prueba 350 y 0 por ejemplo. Debería devolver -10 pero devuelve -350
kjyv
@kjyv No puedo reproducir el comportamiento que describe. ¿Puedes publicar el código exacto?
Laurence Gonsalves
Ah, lo siento He probado exactamente su versión con rad y grados en python nuevamente y funcionó bien. Entonces debe haber sido un error en mi traducción a C # (ya no lo tengo).
kjyv
2
Tenga en cuenta que, a partir de Python 3, ¡puede usar tau de forma nativa! Solo escribe from math import tau.
mhartl
8

Acepto el desafío de proporcionar la respuesta firmada:

def f(x,y):
  import math
  return min(y-x, y-x+2*math.pi, y-x-2*math.pi, key=abs)
David Jones
fuente
1
Ah ... por cierto, la respuesta es una función de Python. Lo siento, estuve en modo Python por un momento. Espero que esté bien.
David Jones
¡Conectaré la nueva fórmula en mi código arriba y veré qué pasa con ella! (gracias ^ _ ^)
Tom J Nowell
1
Estoy bastante seguro de que la respuesta de PeterB también es correcta. Y malvadamente hack. :)
David Jones
44
Pero este no contiene funciones trigonométricas :)
nornagon
¿Cuál es la fórmula equivalente para java? si los ángulos están en grado.
Soley
6

Para los usuarios de UnityEngine, la manera fácil es usar Mathf.DeltaAngle .

Josh
fuente
1
No tiene salida firmada aunque
kjyv
2

Un código eficiente en C ++ que funciona para cualquier ángulo y en ambos: radianes y grados es:

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
fuente
-1

No hay necesidad de calcular funciones trigonométricas. El código simple en lenguaje C es:

#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);
}

deje dif = a - b, en radianes

dif = difangrad(a,b);

deje dif = a - b, en grados

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

Sin pecado, sin cos, sin bronceado ... ¡¡¡solo geometría !!!!

Uli Gue
fuente
77
¡Insecto! Como #define PIV2 como "M_PI + M_PI", no "(M_PI + M_PI)", la línea se arg = arg - PIV2;expande arg = arg - M_PI + M_PIy no hace nada.
canton7