A menor diferença entre 2 ângulos

137

Dados 2 ângulos no intervalo -PI -> PI em torno de uma coordenada, qual é o valor do menor dos 2 ângulos entre eles?

Tendo em conta que a diferença entre PI e -PI não é 2 PI, mas zero.

Exemplo:

Imagine um círculo, com 2 linhas saindo do centro, existem 2 ângulos entre essas linhas, o ângulo que fazem no interior, também conhecido como ângulo menor , e o ângulo que fazem do lado de fora, também conhecido como ângulo maior. Ambos os ângulos, quando somados, formam um círculo completo. Dado que cada ângulo pode caber dentro de um determinado intervalo, qual é o menor valor de ângulos, levando em consideração a rolagem

Tom J Nowell
fonte
2
Eu li três vezes antes de entender o que você quis dizer. Por favor, adicione um exemplo ou explique melhor ...
Kobi
Imagine um círculo, com 2 linhas saindo do centro, existem 2 ângulos entre essas linhas, o ângulo que fazem no interior, também conhecido como ângulo menor, e o ângulo que fazem do lado de fora, também conhecido como ângulo maior. Ambos os ângulos, quando somados, formam um círculo completo. Dado que cada ângulo pode caber dentro de um determinado intervalo, qual é o valor ângulos menores, tendo em conta o rollover
Tom J Nowell
2
@JimG. essa não é a mesma pergunta; nessa questão, o ângulo P1 usado na outra pergunta seria a resposta incorreta, seria o outro ângulo menor. Além disso, não há garantia de que o ângulo é com o eixo horizontal
Tom J Nowell

Respostas:

193

Isso fornece um ângulo assinado para qualquer ângulo:

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

Cuidado em muitos idiomas: a modulooperação retorna um valor com o mesmo sinal que o dividendo (como C, C ++, C #, JavaScript, lista completa aqui ). Isso requer uma modfunção personalizada da seguinte forma:

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

Ou então:

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

Se os ângulos estão dentro de [-180, 180], isso também funciona:

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

De uma maneira mais detalhada:

a = targetA - sourceA
a -= 360 if a > 180
a += 360 if a < -180
bennedich
fonte
Mais simples e faz mais sentido ler em voz alta, embora efetivamente a mesma coisa, primeiras figuras IPV fora o ângulo, segunda parte torna-se sua sempre o menor dos 2 ângulos possíveis
Tom J Nowell
1
embora se queira fazer um% 360, por exemplo, se eu tivesse o ângulo 0 e o ângulo alvo 721, a resposta correta seria 1, a resposta dada acima seria 361
Tom J Nowell
1
Um equivalente mais conciso, embora potencialmente mais caro, da segunda declaração da última abordagem, é a -= 360*sgn(a)*(abs(a) > 180). (Venha para pensar sobre isso, se você tiver implementações sem ramos de sgne abs, em seguida, que o poder característica realmente começar a compensar precisando duas multiplicações.)
mmirate
1
O exemplo "Ângulo assinado para qualquer ângulo" parece funcionar na maioria dos cenários, com uma exceção. No cenário double targetA = 2; double sourceA = 359;'a' será igual a -357,0 em vez de 3.0
Stevoisiak
3
No C ++, você pode usar std :: fmod (a, 360) ou fmod (a, 360) para usar o módulo de ponto flutuante.
Joeppie
145

x é o ângulo alvo. y é a fonte ou o ângulo inicial:

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

Retorna o ângulo delta assinado. Observe que, dependendo da sua API, a ordem dos parâmetros para a função atan2 () pode ser diferente.

Peter B
fonte
13
x-yfornece a diferença de ângulo, mas pode estar fora dos limites desejados. Pense neste ângulo que define um ponto no círculo unitário. As coordenadas desse ponto são (cos(x-y), sin(x-y)). atan2retorna o ângulo para esse ponto (que é equivalente a x-y), exceto que seu intervalo é [-PI, PI].
Max
3
Isso passa no conjunto de testes gist.github.com/bradphelan/7fe21ad8ebfcb43696b8
bradgonesurfing
2
uma solução simples de uma linha e resolvida para mim (não a resposta selecionada;)). mas o bronzeado inverso é um processo caro.
Mohan Kumar
2
Para mim, a solução mais elegante. Pena que possa ser computacionalmente caro.
focs
Para mim, a solução mais elegante também! Resolvi meu problema perfeitamente (queria ter uma fórmula que me desse o ângulo de virada assinado , que é o menor dentre as duas direções / ângulos de virada possíveis).
Jürgen Brauer
41

Se seus dois ângulos são x e y, então um dos ângulos entre eles é abs (x - y). O outro ângulo é (2 * PI) - abs (x - y). Portanto, o valor do menor dos 2 ângulos é:

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

Isso fornece o valor absoluto do ângulo e assume que as entradas estão normalizadas (ou seja: dentro da faixa [0, 2π)).

Se você deseja preservar o sinal (ou seja: direção) do ângulo e também aceitar ângulos fora do intervalo, [0, 2π)você pode generalizar o que foi descrito acima. Aqui está o código Python para a versão 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

Observe que o %operador não se comporta da mesma maneira em todos os idiomas, principalmente quando valores negativos estão envolvidos; portanto, se for necessário portar alguns ajustes de sinal.

Laurence Gonsalves
fonte
1
@bradgonesurfing Isso é / era verdade, mas, para ser justo, seus testes verificaram coisas que não foram especificadas na pergunta original, especificamente entradas não normalizadas e preservação de sinais. A segunda versão na resposta editada deve passar nos testes.
Laurence Gonsalves
A segunda versão também não funciona para mim. Tente 350 e 0, por exemplo. Ele deve retornar -10 mas retorna -350
kjyv
@kjyv Não consigo reproduzir o comportamento que você descreve. Você pode postar o código exato?
Laurence Gonsalves
Ah, me desculpe. Testei exatamente sua versão com rad e graus em python novamente e funcionou bem. Então, deve ter sido um erro na minha tradução para C # (não o tenho mais).
kjyv
2
Observe que, a partir do Python 3, você pode realmente usar o tau nativamente! Apenas escreva from math import tau.
mhartl 8/01
8

Eu enfrento o desafio de fornecer a resposta assinada:

def f(x,y):
  import math
  return min(y-x, y-x+2*math.pi, y-x-2*math.pi, key=abs)
David Jones
fonte
1
Ah ... a resposta é uma função Python, a propósito. Desculpe, eu estava no modo Python por um momento. Espero que esteja tudo bem.
David Jones
Vou conectar a nova fórmula no meu código no andar de cima e ver o que acontece com ela! (agradecimento ^ _ ^)
Tom J Nowell
1
Tenho certeza de que a resposta de PeterB também está correta. E maldosamente hackish. :)
David Jones
4
Mas este não contém funções trigonométricas :)
nornagon
Qual é a fórmula equivalente para java? se os ângulos estão em grau.
Soley
6

Para usuários do UnityEngine, a maneira mais fácil é usar o Mathf.DeltaAngle .

Josh
fonte
1
Não tem saída assinada tho
kjyv 25/10/19
2

Um código eficiente em C ++ que funciona para qualquer ângulo e em ambos: radianos e graus é:

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

Não há necessidade de calcular funções trigonométricas. O código simples na linguagem C é:

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

seja dif = a - b, em radianos

dif = difangrad(a,b);

Seja dif = a - b, em graus

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

Sem pecado, sem cos, sem bronzeado, .... apenas geometria !!!!

Uli Gue
fonte
7
Erro! Como você define # PIV2 como "M_PI + M_PI", não "(M_PI + M_PI)", a linha se arg = arg - PIV2;expande para arg = arg - M_PI + M_PIe assim nada faz.
precisa saber é o seguinte