Confrontare gli angoli e capire la differenza


27

Voglio confrontare gli angoli e avere un'idea della distanza tra loro. Per questa applicazione, sto lavorando in gradi, ma funzionerebbe anche per radianti e laureati. Il problema con gli angoli è che dipendono dall'aritmetica modulare, cioè 0-360 gradi.

Supponiamo che un angolo sia a 15 gradi e uno a 45. La differenza è di 30 gradi e l'angolo di 45 gradi è maggiore di quello di 15 gradi.

Ma questo si interrompe quando hai, diciamo, 345 gradi e 30 gradi. Sebbene si confrontino correttamente, la differenza tra loro è di 315 gradi anziché i 45 gradi corretti.

Come posso risolvere questo? Potrei scrivere codice algoritmico:

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

Preferirei una soluzione che eviti confronti / rami e si basi interamente sull'aritmetica.


Su questo problema, possiamo supporre che gli angoli indicati siano nell'intervallo [0,360] o (-infinito, + infinito)? Ad esempio, l'algoritmo dovrebbe funzionare anche sul confronto di -130 gradi con 450?
egarcia,

Supponiamo che gli angoli siano normalizzati a quell'intervallo.
Thomas O

Risposte:


29

Ecco la mia versione semplificata, senza rami, senza confronti, senza min / max:

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

Rimosso il modulo, poiché gli input sono sufficientemente limitati (grazie a Martin per averlo sottolineato).

Due addominali, tre sottratti.


Non è necessario il modulo, i valori di input sono limitati all'intervallo [0,360] (vedere il commento di Thomas all'invio originale). Piuttosto pulito.
Martin Sojka,

Ah, sì, hai ragione. Ho avuto un input meno rigoroso quando l'ho provato.
JasonD

ma cosa succederebbe se volessi preservare il segno della differenza in modo da poter dire quale fosse a sinistra?
Jacob Phillips,

9

Sebbene si confrontino correttamente, la differenza tra loro è di 315 gradi anziché i 45 gradi corretti.

Cosa ti fa pensare che 315 non sia corretto? In una direzione, è di 315 gradi, nell'altra direzione, è di 45. Volete scegliere quale sia il più piccolo dei 2 angoli possibili e questo sembra richiedere intrinsecamente un condizionale. Non è possibile risolverlo con l'aritmetica avvolgente (cioè tramite l'operatore del modulo) perché quando si aumenta gradualmente un angolo l'angolo tra loro cresce fino a quando non raggiunge 180 e quindi inizia a diminuire.

Penso che tu debba controllare entrambi gli angoli e decidere quale direzione vuoi misurare, o calcolare entrambe le direzioni e decidere quale risultato vuoi.


Scusa, dovrei chiarire. Se lo hai fatto al contrario, 30 - 345 è -315 e un angolo negativo non ha molto senso. Immagino che sto cercando l'angolo più piccolo tra i due. cioè 45 gradi è inferiore a 315.
Thomas O

2
Ma non c'è "inversione": hai 2 angoli e 2 tipi di rotazione che puoi eseguire per far combaciare uno con l'altro. Un angolo negativo ha perfettamente senso - dopo tutto è solo una misura della rotazione da un asse arbitrario.
Kylotan,

Se vuoi l'angolo più piccolo, allora gli addominali (a1% 180 - a2% 180) ti daranno quell'angolo. Tuttavia non ti dirà la direzione. La rimozione degli addominali ti darà l'angolo più piccolo "passando da" a1 "a" a2
Chewy Gumball

2
@Chewy, eh? La differenza tra 180 e 0 non è 0 e la differenza tra 181 e 0 non è 1 ...
dash-tom-bang

1
@ dash-tom-bang Hai perfettamente ragione. Non so cosa stavo pensando, ma non era affatto corretto ora che lo guardo di nuovo. Si prega di ignorare il mio commento precedente.
Chewy Gumball,

4

C'è sempre il trucco di fare entrambi i rami e lasciare che il risultato del confronto ne scelga uno:

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

Non conosco un modo per farlo senza confronti , ma di solito il ramo è ciò che rende il codice lento e lungo, non il confronto. Almeno secondo me, questo è più leggibile della risposta di Martin (qualsiasi buon programmatore C lo riconoscerà come un equivalente senza rami e vedrà cosa sta facendo), ma anche meno efficiente.

Ma come ho detto nel mio commento, gli algoritmi senza rami sono buoni su processori con pipeline profonde e cattive previsioni - un microcontrollore di solito ha una pipeline minuscola e un PC desktop di solito ha buone previsioni, quindi a meno che non si stia prendendo di mira una console di gioco, la versione ramificata è probabilmente il percorso migliore se riduce il conteggio delle istruzioni.

Come sempre, la profilazione - che potrebbe essere semplice come il conteggio delle operazioni per il tuo sistema - ti darà la vera risposta.


2

Supponendo che il valore vero sia compreso tra -1 e il falso sia uguale a 0 e '~', '&' e '|' siamo bit a bit non , e ed o gestori, rispettivamente, e stiamo lavorando con l'aritmetica in complemento a due:

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 perché è intelligente, ma su un microcontrollore probabilmente è molto peggiore rispetto alla versione ramificata.

Dipende un po 'dal microcontrollore, ma, sì, di solito non ne vale la pena; un salto condizionale (breve) è in genere abbastanza veloce. Inoltre, la terza e la quinta riga possono essere riscritte per essere un po 'più veloci usando l'operazione xor (^) in questo modo, ma le ho lasciate nella forma corrente per chiarezza: temp1: = temp2 ^ ((temp2 ^ -temp2) & ~ temp1), temp2: = temp1 ^ ((temp1 ^ (360 - temp1)) & ~ temp2)
Martin Sojka,

1

Che dire di questo?

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

L'aggiunta di 360 è lì per evitare differenze negative, perché un modulo di un numero negativo restituisce un risultato negativo. Quindi ottieni il più piccolo dei due risultati possibili.

C'è ancora una decisione implicita, ma non so come evitarlo. Fondamentalmente si confrontano i due angoli calcolando la differenza in senso orario o antiorario, e sembra che si desideri esplicitamente la più piccola di queste due differenze. Non so come ottenere quel risultato senza confrontarli. Cioè, senza usare "abs", "min", "max" o un operatore simile.


Esistono diversi modi per calcolare min, max e abs di ints senza istruzioni di diramazione, sebbene dal momento che si tratta di un microcontrollore il ramo è probabilmente il modo più veloce. graphics.stanford.edu/~seander/bithacks.html#IntegerAbs

1

Mentre la tua domanda non ne ha fatto riferimento, lavorerò sul presupposto che la tua domanda di calcolo dell'angolo derivi dal voler conoscere l'angolo minimo tra due vettori .

Questo calcolo è facile. Supponendo che A e B siano i tuoi vettori:

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

Se non avessi vettori e volessi utilizzare questo approccio, potresti costruire vettori di lunghezza unitaria dati i tuoi angoli new Vector2( cos( angle ), sin ( angle ) ).


1
Il processore su cui sto lavorando è un piccolo microcontrollore. Non ha senso usare le funzioni di trigger per generare un vettore solo per ottenere la differenza tra gli angoli, ogni ciclo è prezioso.
Thomas O

1
Su un microcontrollore sono un po 'sorpreso che non sia meglio usare un ramo, ma non c'è molto aritmico nella mia risposta se vuoi davvero evitare la ramificazione.
JasonD,

Bene, un ramo ha due cicli e un add / sottrazione / etc è un ciclo, ma la ramificazione occupa anche memoria di programma aggiuntiva. Non è critico, ma sarebbe bello.
Thomas O

Ho la sensazione che la tua risposta sia corretta e la mia sia sbagliata, ma non riesco a capire perché sia così. :)
Kylotan,

1

Sostanzialmente uguale alla risposta di JasonD, ad eccezione dell'utilizzo di operazioni bit a bit anziché della funzione di valore assoluto.

Questo presuppone che tu abbia numeri interi corti a 16 bit!

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

credo

delta = (a2 + Math.ceil( -a2 / 360 ) * 360) - (a1 + Math.ceil( -a1 / 360 ) * 360);

0

Dato che ti interessa solo eliminare rami e operazioni "complesse" al di là dell'aritmetica, consiglierei questo:

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

Hai ancora bisogno di un absdentro nonostante tutti gli angoli siano positivi. Altrimenti, verrà sempre scelto il risultato più negativo (e ci sarà sempre esattamente una risposta negativa per positivo, unico aeb quando si confrontano ab e ba).

Nota: ciò non preserverà la direzione tra angle1 e angle2. A volte ne hai bisogno per scopi di intelligenza artificiale.

Questo è simile alla risposta di CeeJay, ma elimina tutti i moduli. Non so quale sia il costo del ciclo abs, ma immagino che sia 1 o 2. Difficile dire anche quale sia il costo min. Forse 3? Quindi, insieme a un ciclo per sottrazione, questa linea dovrebbe avere un costo compreso tra 4 e 9.


0

Ottieni l' angolo relativo più piccolo in forma firmata (+/-), dal punto di vista di have verso want :

  • al massimo 180 gradi | PI radianti
  • -segnata se in senso antiorario
  • + firmato se in senso orario

Gradi

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

radianti

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

Fondamento logico

Mi sono imbattuto in questo thread dopo averlo capito, cercando una soluzione che eviti il ​​modulo; finora non ne ho trovato nessuno . Questa soluzione serve a preservare il segno prospettico, poiché @ jacob-phillips ha chiesto questo commento . Ci sono soluzioni più economiche se hai solo bisogno dell'angolo senza segno più corto.


0

È una vecchia domanda, ma mi sono imbattuto nello stesso caso: dovevo ottenere una differenza angolare firmata e preferibilmente senza rami e matematica pesante. Questo è quello che ho finito con:

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

Il limite è che 'b' non dovrebbe avere più di 'N' rotazioni rispetto a 'a'. Se non puoi assicurarlo e puoi consentire operazioni extra, utilizza questo come prima riga:

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

Ho preso l'idea dal 13 ° commento di questo post: http://blog.lexique-du-net.com/index.php?post/Calculate-the-real-difference-b Between - two - angles - keeping - the- cartello


-1

immagino di poterlo dire

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

naturalmente, considerando che l'angolo viene misurato in gradi.


1
Non credo che questo risolva il problema nella domanda. 345% 360 == 345 e addominali (345-30) sono ancora 315.
Gregory Avery-Weir

@Gregory: okay! Mi dispiace per l'errore. Sto modificando la risposta, controlla questa nuova. :)
Vishnu,

1
A proposito, angle1 = angle1% 360; angle2 = angle2% 360; var distance = Math.abs (angolo1-angolo2); è uguale a var distance = Math.abs (angolo1-angolo2)% 360 - solo più lento.
Martin Sojka,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.