La plus petite différence entre 2 angles

137

Étant donné 2 angles dans la plage -PI -> PI autour d'une coordonnée, quelle est la valeur du plus petit des 2 angles entre eux?

Tenant compte du fait que la différence entre PI et -PI n'est pas de 2 PI mais de zéro.

Exemple:

Imaginez un cercle, avec 2 lignes sortant du centre, il y a 2 angles entre ces lignes, l'angle qu'elles forment à l'intérieur, c'est-à-dire le plus petit angle , et l'angle qu'elles forment à l'extérieur, c'est-à-dire le plus grand angle. Les deux angles lorsqu'ils sont additionnés forment un cercle complet. Étant donné que chaque angle peut tenir dans une certaine plage, quelle est la valeur des angles les plus petits, en tenant compte du survol

Tom J Nowell
la source
2
J'ai lu 3 fois avant de comprendre ce que vous vouliez dire. Veuillez ajouter un exemple, ou expliquer mieux ...
Kobi
Imaginez un cercle, avec 2 lignes partant du centre, il y a 2 angles entre ces lignes, l'angle qu'elles forment à l'intérieur, c'est-à-dire le plus petit angle, et l'angle qu'elles forment à l'extérieur, c'est-à-dire le plus grand angle. Les deux angles lorsqu'ils sont additionnés forment un cercle complet. Étant donné que chaque angle peut tenir dans une certaine plage, quelle est la valeur des angles les plus petits, en tenant compte du survol
Tom J Nowell
2
@JimG. ce n'est pas la même question, dans cette question l'angle P1 utilisé dans l'autre question serait la réponse incorrecte, ce serait l'autre angle plus petit. De plus, il n'y a aucune garantie que l'angle soit avec l'axe horizontal
Tom J Nowell

Réponses:

193

Cela donne un angle signé pour tous les angles:

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

Attention dans de nombreux langages, l' moduloopération renvoie une valeur avec le même signe que le dividende (comme C, C ++, C #, JavaScript, liste complète ici ). Cela nécessite une modfonction personnalisée comme celle-ci:

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

Ou alors:

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

Si les angles sont compris entre [-180, 180], cela fonctionne également:

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

De manière plus verbeuse:

a = targetA - sourceA
a -= 360 if a > 180
a += 360 if a < -180
Bennedich
la source
Plus simple et a plus de sens, lire à haute voix, bien que effectivement la même chose, d'abord bti calcule l'angle, la deuxième partie s'assure que c'est toujours le plus petit des 2 angles possibles
Tom J Nowell
1
bien que l'on puisse vouloir faire un% 360, par exemple si j'avais l'angle 0 et l'angle cible 721, la bonne réponse serait 1, la réponse donnée par ce qui précède serait 361
Tom J Nowell
1
Un équivalent plus concis, bien que potentiellement plus coûteux, du deuxième énoncé de cette dernière approche, est a -= 360*sgn(a)*(abs(a) > 180). (À bien y penser, si vous avez des implémentations sans branche de sgnet abs, alors cette caractéristique pourrait en fait commencer à compenser le besoin de deux multiplications.)
mmirate
1
L'exemple «Angle signé pour n'importe quel angle» semble fonctionner dans la plupart des scénarios, à une exception près. Dans le scénario double targetA = 2; double sourceA = 359;'a' sera égal à -357,0 au lieu de 3,0
Stevoisiak
3
En C ++, vous pouvez utiliser std :: fmod (a, 360) ou fmod (a, 360) pour utiliser modulo en virgule flottante.
Joeppie
145

x est l'angle cible. y est la source ou l'angle de départ:

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

Il renvoie l'angle delta signé. Notez que selon votre API, l'ordre des paramètres de la fonction atan2 () peut être différent.

Peter B
la source
13
x-yvous donne la différence d'angle, mais elle peut être hors des limites souhaitées. Pensez à cet angle définissant un point sur le cercle unité. Les coordonnées de ce point sont (cos(x-y), sin(x-y)). atan2renvoie l'angle pour ce point (qui équivaut à x-y) sauf que sa plage est [-PI, PI].
Max
3
Cela passe la suite de tests gist.github.com/bradphelan/7fe21ad8ebfcb43696b8
bradgonesurfing
2
une solution simple en une ligne et résolue pour moi (pas la réponse choisie;)). mais l'inverse de tan est un processus coûteux.
Mohan Kumar
2
Pour moi, la solution la plus élégante. Dommage que cela puisse coûter cher en calcul.
focs le
Pour moi aussi la solution la plus élégante! J'ai parfaitement résolu mon problème (je voulais avoir une formule qui me donne l' angle de virage signé qui est le plus petit des deux directions / angles de virage possibles).
Jürgen Brauer
41

Si vos deux angles sont x et y, l'un des angles entre eux est abs (x - y). L'autre angle est (2 * PI) - abs (x - y). Ainsi, la valeur du plus petit des 2 angles est:

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

Cela vous donne la valeur absolue de l'angle et suppose que les entrées sont normalisées (c'est-à-dire: dans la plage [0, 2π)).

Si vous souhaitez conserver le signe (c'est-à-dire la direction) de l'angle et accepter également des angles en dehors de la plage, [0, 2π)vous pouvez généraliser ce qui précède. Voici le code Python pour la version généralisée:

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

Notez que l' %opérateur ne se comporte pas de la même manière dans toutes les langues, en particulier lorsque des valeurs négatives sont impliquées, donc en cas de portage, des ajustements de signe peuvent être nécessaires.

Laurence Gonsalves
la source
1
@bradgonesurfing C'est / était vrai, mais pour être juste, vos tests ont vérifié des choses qui n'étaient pas spécifiées dans la question d'origine, en particulier les entrées non normalisées et la préservation des signes. La deuxième version de la réponse modifiée devrait réussir vos tests.
Laurence Gonsalves
La deuxième version ne fonctionne pas non plus pour moi. Essayez 350 et 0 par exemple. Il devrait renvoyer -10 mais renvoie -350
kjyv
@kjyv Je ne peux pas reproduire le comportement que vous décrivez. Pouvez-vous publier le code exact?
Laurence Gonsalves
Ah, je suis désolé. J'ai testé à nouveau exactement votre version avec rad et degrés en python et cela a bien fonctionné. Cela a dû être une erreur dans ma traduction en C # (ne l'avez plus).
kjyv
2
Notez qu'à partir de Python 3, vous pouvez en fait utiliser tau de manière native! Écrivez juste from math import tau.
mhartl
8

Je relève le défi de fournir la réponse signée:

def f(x,y):
  import math
  return min(y-x, y-x+2*math.pi, y-x-2*math.pi, key=abs)
David Jones
la source
1
Ah ... la réponse est une fonction Python au fait. Désolé, j'étais en mode Python pendant un moment. J'espère que ça va.
David Jones
Je vais brancher la nouvelle formule dans mon code à l'étage et voir ce qu'il en advient! (merci ^ _ ^)
Tom J Nowell
1
Je suis presque sûr que la réponse de PeterB est également correcte. Et diaboliquement hackish. :)
David Jones
4
Mais celui-ci ne contient pas de fonctions trigonométriques :)
nornagon
Quelle est la formule équivalente pour java? si les angles sont en degrés.?
Soley
6

Pour les utilisateurs d'UnityEngine, le moyen le plus simple consiste simplement à utiliser Mathf.DeltaAngle .

Josh
la source
1
N'a pas de sortie signée tho
kjyv
2

Un code efficace en C ++ qui fonctionne pour n'importe quel angle et dans les deux: radians et degrés est:

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
la source
-1

Il n'est pas nécessaire de calculer des fonctions trigonométriques. Le code simple en langage C est:

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

soit dif = a - b, en radians

dif = difangrad(a,b);

soit dif = a - b, en degrés

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

Pas de péché, pas de cos, pas de bronzage, .... seulement la géométrie !!!!

Uli Gue
la source
7
Punaise! Puisque vous #define PIV2 comme "M_PI + M_PI", pas "(M_PI + M_PI)", la ligne se arg = arg - PIV2;développe arg = arg - M_PI + M_PIet ne fait rien.
canton7